مدیریت خطاها در جاوا اسکریپت

30 بهمن 1397
javascript-errors-handling

با سلام، در این قسمت از سری آموزشی برنامه نویسی جاوا اسکریپت قصد داریم راجع به خطاها و به خصوص در رابطه با ساختار throw \ try ... catch صحبت کنیم.

خطاها همیشه اتفاق می‌افتند

خیلی از ما به اشتباه تصور می کنیم که خطا ها در برنامه ی افراد مبتدی اتفاق می افتد اما واقعیت این است که حتی افراد بسیار حرفه ای نیز از خطا مصون نیستند. هر کسی که باشید و هر کجا که باشید و با هر زبانی که برنامه نویسی کنید، چه بخواهید چه نخواهید به خطا برخورد خواهید کرد بنابراین باید بدانید با آن ها چه کار کنید.

در ابتدا می خواهم چند دستور مهم را توضیح بدهم:

  • با دستور try میتوان یک گروه کد را چک کرد تا متوجه شد آیا خطایی در آن ها وجود دارد یا خیر.
  • با دستور catch می توان آن خطا را مدیریت کرد
  • با دستور throw می توان پیام های خطا های شخصی سازی شده ایجاد کرد.
  • با دستور finally می توان پس از استفاده از try ... catch، آن گروه کد را اجرا کرد چه دارای خطا باشد چه نباشد

به این مثال از یک خطا دقت کنید:

<!DOCTYPE html>
<html>
<body>

<p id="demo"></p>

<script>
try {
  adddlert("Welcome guest!");
}
catch(err) {
  document.getElementById("demo").innerHTML = err.message;
}
</script>

</body>
</html>

خروجی این کد عبارت "adddlert is not defined" خواهد بود. این خطا به ما می گوید شما از تابعی به نام adddlert استفاده کرده اید اما چنین تابعی وجود ندارد؛ نه از توابع پیش فرض جاوا اسکریپت است و نه خودتان آن را تعریف کرده اید. به ساختار کد بالا خوب دقت کنید.

ساختار try ... catch

ساختار کلی این دستور به شکل زیر است:

try {
  Block of code to try
}
catch(err) {
  Block of code to handle errors
}

دستور try در فارسی یعنی "امتحان کن" بنابراین می توان فهمید که این دستور کدهایی را که از ما می گیرد امتحان می کند؛ یعنی یک بار اجرایشان می کند تا ببیند اجرا می شوند یا به خطا بر می خورند.

دستور catch در فارسی یعنی "بگیر". این قسمت چه چیزی را می گیرد؟ اگر خطایی در قسمت try اتفاق بیوفتد، قسمت catch آن را می گیرد و ما باید به آن بگوییم که با این خطا چه کار کند.

این دو دستور همیشه با هم استفاده می شوند و استفاده ی جداگانه از آن ها معنایی ندارد.

دستور throw در خطا ها

در حالت عادی، زمانی که جاوا اسکریپت به خطایی برخورد می کند، توقف می کند و یک پیام خطا به شما تحویل می دهد. اصطلاح فنیِ این پدیده throw an exception یا throw an error  است که به فارسی می شود "یک استثناء پرتاب کن" یا "یک خطا پرتاب کن". در واقع اتفاقی که می افتد این است که جاوا اسکریپت یک شیِء خطا می سازد که دو خصوصیت (property) دارد: name و message.

بنابراین با دستو throw می توانیم یک پیام خطا بر اساس سلیقه ی خودمان ایجاد کنیم.

مثالی از اعتبارسنجی داده های ورودی

ما در این مثال داده ی ورودی را می سنجیم. اگر مقدار اشتباه باشد یک Exception (همان err در کد زیر) را throw (پرتاب) می کنیم. سپس Exception توسط دستور catch گرفته می شود و یک پیام خطای شخصی سازی شده نمایش داده می شود. پیام خطای شخصی سازی شده یعنی پیام خطایی که خودِ ما آن را تعریف کرده ایم و گفته ایم در صورت بروز خطا چه چیزی نمایش بدهد. در حالت عادی این چنین نیست و جاوا اسکریپت برای انواع خطا ها پیام هایی دارد که از آن ها استفاده می کند.

مثال ما از این قرار است:

<!DOCTYPE html>
<html>
<body>

<p>Please input a number between 5 and 10:</p>

<input id="demo" type="text">
<button type="button" onclick="myFunction()">Test Input</button>
<p id="p01"></p>

<script>
function myFunction() {
  var message, x;
  message = document.getElementById("p01");
  message.innerHTML = "";
  x = document.getElementById("demo").value;
  try { 
    if(x == "")  throw "empty";
    if(isNaN(x)) throw "not a number";
    x = Number(x);
    if(x < 5)  throw "too low";
    if(x > 10)   throw "too high";
  }
  catch(err) {
    message.innerHTML = "Input is " + err;
  }
}
</script>

</body>
</html>

این مثال به صورت interactive (تعاملی) طراحی شده است؛ یعنی شما باید داده ای وارد آن کنید و روی دکمه اش کلیک کنید تا مقداری نمایش بدهد بنابراین خروجی آن فقط در ادیتور آنلاین جاوا اسکریپت قابل مشاهده است. پس از اینکه به ادیتور آنلاین رفتید، ابتدا کد را به دقت مطالعه کنید، سپس بر اساس کدها، مقادیر مختلفی به آن بدهید. به طور مثال ابتدا ورودی را خالی بگذارید و سپس test input را بزنید، بار دیگر یک عدد به آن بدهید، بار دیگر یک رشته و همین طور الی آخر.

نکته: مثال بالا صرفا جهت یادگیری بود. در مرورگر های مدرن راه های زیادی برای اعتبار سنجی داده ها وجود دارد که استفاده ی ترکیبی از HTML و JavaScript مشهور ترین و محبوب ترین روش آن است.

به طور مثال می توانید در HTML از attribute های زیر استفاده کنید:

<input id="demo" type="number" min="5" max="10" step="1"

البته نباید نگران بود، در فصل های بعدی به طور مفصل با اعتبار سنجی داده ها و فرم ها آشنا خواهیم شد.

دستور finally

با استفاده از دستور finally می توانید کد یا کدهایی را پس از ساختار try ... catch اجرا کنید، حتی اگر ساختار try ... catch به شما خطا تحویل دهد. ساختار کلی این دستور به شکل زیر است:

try {
  Block of code to try
}
catch(err) {
  Block of code to handle errors
} 
finally {
  Block of code to be executed regardless of the try / catch result
}

به مثال زیر توجه کنید:

<!DOCTYPE html>
<html>
<body>

<p>Please input a number between 5 and 10:</p>

<input id="demo" type="text">
<button type="button" onclick="myFunction()">Test Input</button>

<p id="p01"></p>

<script>
function myFunction() {
  var message, x;
  message = document.getElementById("p01");
  message.innerHTML = "";
  x = document.getElementById("demo").value;
  try { 
    if(x == "")  throw "is empty";
    if(isNaN(x)) throw "is not a number";
    x = Number(x);
    if(x > 10)   throw "is too high";
    if(x < 5)  throw "is too low";
  }
  catch(err) {
    message.innerHTML = "Input " + err;
  }
  finally {
    document.getElementById("demo").value = "";
  }
}
</script>

</body>
</html>

خروجی این کد تنها در ادیتور آنلاین جاوا اسکریپت قابل مشاهده است. پس از وارد شدن به ادیتور آنلاین جاوا اسکریپت با کد بازی کنید تا بفهمید فرق آن با مثال جلسه ی قبل چیست.

آیا متوجه شدید؟ ما گفتیم که زمانی از دستور finally استفاده می کنیم که نتیجه ی ساختار try ... catch (وجود خطا یا عدم وجود خطا) برایمان اهمیتی نداشته باشد. در این مثال گفته ایم جه عدد بود، چه رشته بود، چه خالی بود و خلاصه هر اشکالی داشت یا نداشت، مقدار ورودی را پاک کن. منظور ما همان چیزی است که در HTML تایپ می کنید، یعنی این قسمت از کد:

<input id="demo" type="text">

بنابراین دستور زیر می گوید:

finally {
    document.getElementById("demo").value = "";
  }

عنصری را که دارای آیدی demo است پیدا کرده و مقدارش را خالی کن.

در مثال قبل چنین مقداری وجود نداشت و ورودی شما در box مربوطه باقی می ماند.

شیِء خطا در جاوا اسکریپت

شیء خطا در جاوا اسکریپت در جدول زیر خلاصه می شود:

خصوصیت (property) توضیحات
name یا نامِ خطا را تعیین کرده و یا آن را دریافت می کند
message یا پیامِ خطا (محتوای خطا) را تعیین کرده و یا آن را دریافت می کند

خصوصیت name می تواند شش مقدار مختلف را برگرداند:

نام خطا توضیحات
EvalError خطایی در تابع ()eval رخ داده است.
RangeError عدد "خارج از محدوده" است رخ داده است.
ReferenceError یک reference غیر مجاز اتفاق افتاده است.
SyntaxError خطای syntax (ساختار نوشتار، دستور لغت برنامه نویسی) رخ داده است.
TypeError خطای نوع (type) رخ داده است.
URIError خطایی در تابع ()encodeURI رخ داده است.

ما به بررسی تک تک این موارد می پردازیم

ساختار EvalError

EvalError به بروز خطایی در تابع ()eval اشاره دارد. اگر با این تابع آشنایی ندارید به این لینک مراجعه کنید.

نکته: در نسخه های جدید جاوا اسکریپت، دیگر خطای EvalError پرتاب نمی شود و باید به جای آن از SyntaxError استفاده کنید.

ساختار RangeError

اگر عددی خارج از محدوده ی مجاز در جاوا اسکریپت باشد، خطای RangeError پرتاب خواهد شد. بگذارید یک مثال بزنم؛ آیا متد toPrecision را به یاد دارید؟ اگر با این متد آشنا نیستید به مقاله ی متد ها در جاوا اسکریپت: اعداد و قسمت متد "()toPrecision" مراجعه کنید. اگر این متد را به یاد دارید حتما می دانید که کد زیر غلط است:

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Errors</h2>

<p>You cannot set the number of significant digits of a number to 500:</p>

<p id="demo">

<script>
var num = 1;
try {
  num.toPrecision(500);
}
catch(err) {
  document.getElementById("demo").innerHTML = err.name;
}
</script>

</body>
</html>

نمی شود تعداد ارقام اصلی یک عدد 500 تا باشد!! شاید در ریاضی بشود اما در برنامه نویسی محدودیت هایی داریم، بنابراین اجازه ی استفاده از این مقدار را ندارید. خروجی این کد عبارت "RangeError" خواهد بود.

ساختار ReferenceError

Reference به معنی ارجاع دادن است. در واقع اگر شما از یک متغیری که تعریف نشده است استفاده کنید (یا به قول فنی تر، به آن ارجاع دهید) با خطای ReferenceError  مواجه خواهید شد. مثال:

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Errors</h2>

<p>You cannot use the value of a non-existing variable:</p>

<p id="demo"></p>

<script>
var x;
try {
  x = y + 1;
}
catch(err) {
  document.getElementById("demo").innerHTML = err.name;
}
</script>

</body>
</html>

در کد بالا متغیر y اصلا تعریف نشده است اما بدون مقدمه در کد استفاده شده است! خروجی این کد خطای "ReferenceError" خواهد بود.

ساختار SyntaxError

اگر بخواهید کدی را که دارای مشکل syntax است داخل تابع ()eval قرار دهید به خطای SyntaxError برمیخورید:

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Errors</h2>

<p>You cannot evaluate code that contains a syntax error:</p>

<p id="demo"></p>

<script>
try {
  eval("alert('Hello)");
}
catch(err) {
  document.getElementById("demo").innerHTML = err.name;
}
</script>

</body>
</html>

گفتیم که syntax همان دستور زبان برای زبان های برنامه نویسی است؛ اینکه ویرگول ها کجا قرار بگیرند، پرانتز ها چطور و ... . دستور (alert('Hello دراای خطای syntax است چرا که quotation دوم جا افتاده است. صورت صحیح این دستور به شکل ('alert('Hello است. مشخصا خروجی این کد "SyntaxError" خواهد بود.

ساختار TypeError

اگر مقداری را استفاده کنید که از نظر جاوا اسکریپت "نوع" صحیح نباشد به خطای TypeError برخورد خواهید کرد:

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Errors</h2>

<p>You cannot convert a number to upper case:</p>

<p id="demo"></p>

<script>
var num = 1;
try {
  num.toUpperCase();
}
catch(err) {
  document.getElementById("demo").innerHTML = err.name;
}
</script>

</body>
</html>

مبحث uppercase و lowercase (بزرگی و کوچکی حروف زبان انگلیسی) مربوط به حروف است نه اعداد. بنابراین 1 به صورت uppercase اصلا معنا نمی دهد و با خطای "TypeError" مواجه می شوید.

ساختار URIError

در واقع URI مخفف (Uniform Resource Identifier) به معنای «شناسه ی منبع یکسان» است. بر اساس تعریف ویکی پدیا:

یوآرآی یا شناسانهٔ منبع یکسان (به انگلیسی: URI) در علوم رایانه به رشته‌ای از نویسه‌ها گفته می‌شود که برای شناسایی یک نام یا منبع روی اینترنت بکار می‌رود. چنین شناسه‌ای این امکان را فراهم می‌کند تا منابع مختلف روی یک شبکه (معمولاً جهان وب) با یکدیگر به وسیلهٔ قراردادهایی مشخص، تعامل داشته باشند. رایج‌ترین شکل یوآرآی نشانی وب است.

این تعریف قلمبه و سلمبه ی URI است و شما نیازی ندارید توضیح فنی آن را بلد باشید.

فقط بدانید URI رشته ای از کاراکتر ها است که برای پیدا کردن یک منبع خاص در اینترنت به کار می رود. مانند چه چیزی؟ همین URL ها و آدرس وب سایت ها.

آشنایی در همین حد با URI برای کار ما کافی است. همانطور که می دانید اجازه ی استفاده از کاراکتر های خاصی را در URI ندارید و اگر از این کاراکتر ها در یکی از توابع URI استفاده کنید با خطای URIError برخورد خواهید کرد:

<!DOCTYPE html>
<html>
<body>

<h2>JavaScript Errors</h2>

<p>Some characters cannot be decoded with decodeURI():</p>

<p id="demo"></p>

<script>
try {
  decodeURI("%%%");
}
catch(err) {
  document.getElementById("demo").innerHTML = err.name;
}
</script>

</body>
</html>

به طور مثال در کد بالا نمی توان کاراکتر % را decode کرد، به همین دلیل با خطا مواجه می شویم.

نکته: ماکروسافت و موزیلا از خصوصیات شیء خطای غیر استانداردی استفاده می کنند که خودشان ابداع کرده اند:

fileName (Mozilla)
lineNumber (Mozilla)
columnNumber (Mozilla)
stack (Mozilla)
description (Microsoft)
number (Microsoft)

هیچ وقت از این خصوصیات در وب سایت خود استفاده نکنید، چرا که تنها روی مرورگر های خاصی کار می کند.

هدف؟

این همه حرف زدیم که چه؟ هدفمان چه بود؟ شاید در نگاه اول نیازی به این دستورات نداشته باشیم اما این دستورات در هنگام توسعه ی برنامه هایتان به شدت مفید خواهند بود. با این ساختار ها، مانند try ... catch، می توانید خطا های برنامه هایتان را به سرعت پیدا کنید و مثل برنامه نویسان مبتدی گیج نزنید!

امیدوارم از این قسمت لذت برده باشید.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری آموزش جاوا اسکریپت | Javascript توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

احمدرضا
13 اسفند 1400
بسیار جامع و کامل بدون نقص ممنونم زحمت کشیدید.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.