۵ فوریه ۲۰۲۲

مدیریت ارور، "try...catch"

مهم نیست که چقدر در برنامه‌نویسی عالی هستیم، گاهی اوقات اسکریپت‌های ما ارورهایی (error) دارند. این ارورها ممکن است به دلیل اشتباهات ما، ورودی غیر منتظره کاربر، پاسخ نادرست سرور و هزاران دلیل دیگر رخ بدهند.

معمولا، هنگامی که اروری رخ می‌دهد اسکریپت می‌میرد (بلافاصله متوقف می‌شود) و آن را در کنسول چاپ می‌کند.

اما یک ساختار سینتکسی try...catch وجود دارد که به ما این امکان را می‌دهد که ارورها را «بگیریم (catch)» تا اسکریپت، به جای مردن، بتواند کاری منطقی‌تر انجام دهد.

سینتکس “try…catch”

ساختار try...catch دو بلوک اصلی دارد: try و سپس catch:

try {

  // ...کد

} catch (err) {

  // مدیریت ارور

}

این سینتکس اینگونه کار می‌کند:

  1. ابتدا، کد درون try {...} اجرا می‌شود.
  2. اگر اروری وجود نداشت، سپس catch (err) نادیده گرفته می‌شود: اجرای برنامه به انتهای try می‌رسد و با گذشتن از catch ادامه می‌یابد.
  3. اگر اروری رخ دهد، سپس اجرای try متوقف شده و کنترل برنامه به ابتدای catch (err) می‌رود. متغیر err (می‌توانیم هر نامی برای آن استفاده کنیم) شامل شیء اروری حاوی جزئیاتی درباره چیزی که اتفاق افتاده است.

پس یک ارور درون بلوک try {...} اسکریپت را نمی‌کشد – ما شانسی برای مدیریت آن درون catch داریم.

بیایید به چند مثال نگاهی بیاندازیم.

  • یک مثال بدون ارور: alert خطوط (1) و (2) را نشان می‌دهد:

    try {
    
      alert('ابتدای try اجرا می‌شود');  // (1) <--
    
      // اروری اینجا وجود ندارد...
    
      alert('انتهای try اجرا می‌شود');   // (2) <--
    
    } catch (err) {
    
      alert('نادیده گرفته می‌شود چون اروری وجود ندارد Catch'); // (3)
    
    }
  • مثالی شامل یک ارور: خطوط (1) و (3) را نمایش می‌دهد:

    try {
    
      alert('ابتدای try اجرا می‌شود');  // (1) <--
    
      lalala; // !ارور، متغیر تعریف نشده است
    
      alert('(هیچ گاه به اینجا نمی‌رسد) try انتهای');  // (2)
    
    } catch (err) {
    
      alert(`ارور رخ داد!`); // (3) <--
    
    }
try...catch فقط برای ارورهای هنگام اجرای برنامه کار می‌کند

برای اینکه try...catch کار کند، کد باید قابل اجرا باشد. به عبارتی دیگر، باید کد جاوااسکریپت معتبر باشد.

اگر کد از لحاظ سینتکسی غلط باشد کار نمی‌کند، برای مثال اگر آکولادهای بی‌همتا داشته باشد:

try {
  {{{{{{{{{{{{
} catch (err) {
  alert("موتور جاوااسکریپت نمی‌تواند این کد را متوجه شود. این کد نامعتبر است.");
}

موتور جاوااسکریپت ابتدا کد را می‌خواند و سپس آن را اجرا می‌کند. ارورهایی که در فاز خواندن رخ می‌دهند، ارورهای «زمان تجزیه (parse-time errors)» نامیده می‌شوند و قابل پوشش نیستند (از درون همان کد). به این دلیل که موتور نمی‌تواند کد را متوجه شود.

پس try...catch تنها می‌تواند ارورهایی که در کد معتبر رخ می‌دهند را مدیریت کند. چنین ارورهایی «ارورهای هنگام اجرا (runtime errors)» یا گاهی اوقات «استثناها (exceptions)» نامیده می‌شوند.

try...catch به صورت همگام کار می‌کند

اگر یک استثناء در کدی «برنامه‌ریزی شده» رخ دهد، مثلا در setTimeout، سپس try...catch آن را نمی‌گیرد:

try {
  setTimeout(function() {
    noSuchVariable; // اسکریپت اینجا می‌میرد
  }, 1000);
} catch (err) {
  alert( "کار نخواهد کرد" );
}

به این دلیل که خود تابع بعدا اجرا می‌شود، زمانی که موتور ساختار try...catch را پشت سر گذاشته است.

برای اینکه استثناء را درون یک تابع برنامه‌ریزی شده بگیریم، try...catch باید درون آن تابع باشد:

setTimeout(function() {
  try {
    noSuchVariable; // !ارور را مدیریت می‌کند try...catch
  } catch {
    alert( "ارور اینجا گرفته می‌شود!" );
  }
}, 1000);

شیء Error

زمانی که یک ارور رخ می‌دهد، جاوااسکریپت شیءای حاوی جزئیاتی درباره آن را ایجاد می‌کند. این شیء به عنوان آرگومان به catch پاس داده می‌شود:

try {
  // ...
} catch (err) { // <-- استفاده کنیم err این «شیء ارور» است، می‌توانستیم از کلمه‌ای دیگر به جای
  // ...
}

برای تمام ارورهای درون‌ساخت، شیء ارور دو ویژگی اصلی دارد:

name
اسم ارور. برای مثال، برای یک متغیر تعریف نشده برابر با "ReferenceError" است.
message
پیام متنی درباره جزئیات ارور.

در اکثر محیط‌ها ویژگی‌های غیر استاندارد دیگر هم وجود دارد. یکی از ویژگی‌هایی که به طور گسترده استفاده و پشتیبانی می‌شود:

stack
پشته فراخوانی کنونی: رشته‌ای حاوی اطلاعاتی درباره دنباله فراخوانی‌هایی که موجب رخ دادن ارور شدند. برای اهداف اشکال‌زدایی استفاده می‌شود.

برای مثال:

try {
  lalala; // !ارور، متغیر تعریف نشده است
} catch (err) {
  alert(err.name); // ReferenceError
  alert(err.message); // lalala is not defined
  alert(err.stack); // ReferenceError: lalala is not defined at (...پشته فراخوانی‌ها)

  // می‌توانستیم ارور را به طور کامل هم نشان دهیم
  // به رشته تبدیل می‌شود «name: message» ارور به صورت
  alert(err); // ReferenceError: lalala is not defined
}

پیوند اختیاری «catch»

به تازگی اضافه شده است
این قسمت به تازگی به زبان اضافه شده است. مرورگرهای قدیمی ممکن است به پلیفیل نیاز داشته باشند.

اگر ما به جزئیات ارور نیازی نداریم، catch می‌تواند آن را حذف کند:

try {
  // ...
} catch { // <-- (err) بدون
  // ...
}

استفاده از “try…catch”

بیایید یک مورد استفاده از try...catch را در دنیای واقعی ببینیم.

همانطور که از قبل می‌دانیم، جاوااسکریپت از متد JSON.parse(str) برای خواندن مقدارهایی که به صورت جی‌سان کدگذاری شده‌اند پشتیبانی می‌کند.

این متد معمولا برای کدبرداری داده‌ای که از طریق شبکه دریافت شده است، از سرور یا منبعی دیگر، استفاده می‌شود.

ما داده را دریافت می‌کنیم و JSON.parse را اینگونه فراخوانی می‌کنیم:

let json = '{"name":"John", "age": 30}'; // داده دریافت شده از سرور

let user = JSON.parse(json); // تبدیل نمایش متنی به شیء جاوااسکریپت

// شیءای حاوی ویژگی‌های دریافت شده از رشته است user حالا
alert( user.name ); // John
alert( user.age );  // 30

شما می‌توانید در فصل متدهای JSON، toJSON اطلاعاتی با جزئیات بیشتر درباره جی‌سان پیدا کنید.

اگر json شکل درستی نداشته باشد، JSON.parse یک ارور ایجاد می‌کند، پس اسکریپت «می‌میرد».

آیا ما باید به آن راضی باشیم؟ قطعا نه!

اینگونه، اگر داده مشکلی داشته باشد، بازدید کننده هرگز آن را نخواهد دانست (مگر اینکه آن‌ها کنسول توسعه‌دهنده را باز کنند). و مردم چیزی که بدون پیام اروری «می‌میرد» را دوست ندارند.

بیایید از try...catch برای مدیریت ارور استفاده کنیم:

let json = "{ bad json }";

try {

  let user = JSON.parse(json); // <-- ...زمانی که اروری رخ می‌دهد
  alert( user.name ); // کار نمی‌کند

} catch (err) {
  // اجرای برنامه به اینجا می‌پرد...
  alert( "پوزش می‌خواهیم، داده دارای ارور است، ما سعی خواهیم کرد یک بار دیگر برای آن درخواست کنیم." );
  alert( err.name );
  alert( err.message );
}

اینجا ما از بلوک catch فقط برای نمایش پیام استفاده می‌کنیم، اما می‌توانیم کارهای بیشتری انجام دهیم: یک درخواست شبکه جدید ارسال کنیم، یک راه جایگزین به بازدیدکننده پیشنهاد کنیم، اطلاعاتی درباره ارور را به logging facility ارسال کنیم و… . هر چیزی از مردن بهتر است.

پرتاب ارورهای خودمان

اگر json از لحاظ سینتکس درست باشد اما ویژگی مورد نیاز name را نداشته باشد چه؟

مثل اینجا:

let json = '{ "age": 30 }'; // داده ناقض

try {

  let user = JSON.parse(json); // <-- اروری وجود ندارد
  alert( user.name ); // !وجود ندارد name ویژگی

} catch (err) {
  alert( "اجرا نمی‌شود" );
}

اینجا JSON.parse به صورت طبیعی اجرا می‌شود اما در واقع نبودن name برای ما یک ارور است.

برای یکی کردن مدیریت ارور، ما از عملگر throw استفاده می‌کنیم.

عملگر «Throw»

عملگر