۵ فوریه ۲۰۲۲

مدیریت ارور، "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»

عملگر throw (به معنی پرتاب کردن) یک ارور ایجاد می‌کند.

سینتکس آن:

throw <error object>

از لحاظ فنی ما می‌توانیم از هر چیزی به عنوان شیء ارور استفاده کنیم. حتی ارور می‌تواند یک مقدار اصلی باشد،مثل یک عدد یا رشته، اما بهتر است از شیءها استفاده کنیم که ترجیحا ویژگی‌های name و message را داشته باشند (برای اینکه تا حدی با ارورهای درون‌ساخت سازگار باشند).

جاوااسکریپت تابع‌های سازنده درون‌ساخت زیادی برای ارورهای استاندارد دارد: Error، SyntaxError، ReferenceError، TypeError و بقیه آن‌ها. ما می‌توانیم از آن‌ها برای ایجاد شیءهای ارور هم استفاده کنیم.

سینتکس آن‌ها:

let error = new Error(message);
// یا
let error = new SyntaxError(message);
let error = new ReferenceError(message);
// ...

برای ارور‌های درون‌ساخت (نه برای هر شیءای، فقط برای ارورها)، ویژگی name دقیقا اسم سازنده است. و message از آرگومان گرفته می‌شود.

برای مثال:

let error = new Error("اتفاقاتی رخ می‌دهد o_O");

alert(error.name); // Error
alert(error.message); // o_O اتفاقاتی رخ می‌دهد

بیایید ببینیم JSON.parse چه نوع اروری ایجاد می‌کند:

try {
  JSON.parse("{ bad json o_O }");
} catch (err) {
  alert(err.name); // SyntaxError
  alert(err.message); // Unexpected token b in JSON at position 2
}

همانطور که می‌بینیم یک SyntaxError است.

و در این مورد ما، نبودن name یک ارور است چون کاربران باید یک name داشته باشند.

پس بیایید آن را throw کنیم:

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

try {

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

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name"); // (*)
  }

  alert( user.name );

} catch (err) {
  alert( "JSON Error: " + err.message ); // JSON Error: Incomplete data: no name
}

در خط (*)، عملگر throw با message داده شده یک SyntaxError ایجاد می‌کند، به همان شیوه که جاوااسکریپت خودش این ارور را ایجاد می‌کند. اجرای try بلافاصله متوقف می‌شود و کنترل به catch می‌پرد.

حالا catch به جایی برای مدیریت تمام ارورها تبدیل شد: هم برای JSON.parse` و هم برای موارد دیگر.

پرتاب دوباره (Rethrowing)

در مثال بالا ما از try...catch برای مدیریت داده نادرست استفاده می‌کنیم. اما آیا ممکن است که ارور غیر منتظره دیگری درون بلوک try{...} رخ دهد؟ مثلا یک ارور برنامه‌نویسی (متغیر تعریف نشده باشد) یا چیز دیگری، نه فقط موضوع «داده نادرست».

برای مثال:

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

try {
  user = JSON.parse(json); // را قرار دهیم «let» کلمه user یادمان رفت که قبل از

  // ...
} catch (err) {
  alert("JSON Error: " + err); // JSON Error: ReferenceError: user is not defined
  // (نیست JSON Error در واقع)
}

قطعا، هر چیزی ممکن است! برنامه‌نویسان حتما اشتباهاتی می‌کنند. حتی در تسهیلات متن‌باز (open-source) که برای ده‌ها سال میلیون‌ها بار استفاده شده‌اند – ناگهان یک خطا یا باگ (bug) ممکن است کشف شود که منجر به رخنه‌های وحشتناک می‌شود.

در این مورد ما، try...catch برای گرفتن ارورهای «داده نادرست» قرار داده شده است.اما به خاطر ذات آن، catch تمام ارورها را از try دریافت می‌کند. اینجا، این بلوک یک ارور غیر منتظره دریافت می‌کند اما هنوز پیام "JSON Error" یکسان را نشان می‌دهد. این غلط است و همچنین اشکال‌زدایی کد را دشوارتر می‌کند.

برای جلوگیری از چنین مشکلاتی، می‌توانیم تکنیک «پرتاب دوباره (rethrowing)» را به کار ببریم. قانون ساده است:

بلوک catch فقط باید ارورهایی را پردازش کند که آن‌ها را می‌شناسد و بقیه آن‌ها را «rethrow» کند.

تکنیک «rethrowing» می‌تواند می‌تواند اینگونه با جزئیات بیشتری توضیح داده شود:

  1. تمام ارورها را دریافت کن.
  2. در بلوک catch (err) {...} ما شیء ارور err را آنالیز می‌کنیم.
  3. اگر نمی‌دانیم که چگونه آن را مدیریت کنیم، throw err را انجام می‌دهیم.

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

try {
  user = { /*...*/ };
} catch (err) {
  if (err instanceof ReferenceError) {
    alert('ReferenceError'); // برای دسترسی به یک متغیر تعریف نشده «ReferenceError»
  }
}

همچنین می‌توانیم از ویژگی err.name اسم کلاس ارور را دریافت کنیم. تمام ارورهای نیتیو (برای خود زبان) آن را دارند. گزینه دیگر خواندن err.constructor.name است.

در کد پایین، ما از rethrowing استفاده می‌کنیم تا catch فقط SyntaxError را مدیریت کند:

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

  let user = JSON.parse(json);

  if (!user.name) {
    throw new SyntaxError("Incomplete data: no name");
  }

  blabla(); // ارور غیر منتظره

  alert( user.name );

} catch (err) {

  if (err instanceof SyntaxError) {
    alert( "JSON Error: " + err.message );
  } else {
    throw err; // rethrow (*)
  }

}

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

پس بلوک catch در واقع فقط ارورهایی که می‌داند چگونه با آن‌ها مدارا کند را مدیریت می‌کند و بقیه ارورها را «از قلم می‌اندازد».

مثال پایین نشان می‌دهد که چنین ارورهایی چگونه می‌توانند توسط یک سطح بالاتر از try...catch گرفته شوند:

function readData() {
  let json = '{ "age": 30 }';

  try {
    // ...
    blabla(); // !ارور
  } catch (err) {
    // ...
    if (!(err instanceof SyntaxError)) {
      throw err; // rethrow (نمی‌دانیم چگونه آن را کنترل کنیم)
    }
  }
}

try {
  readData();
} catch (err) {
  alert( "External catch got: " + err ); // !آن را گرفتیم
}

اینجا readData فقط می‌داند که SyntaxError را چگونه مدیریت کند در حالی که try...catch بیرونی می‌داند چگونه همه چیز را مدیریت کند.

ساختار try…catch…finally

صبر کنید، این همه چیز نیست.

ساختار try...catch می‌تواند یک بند دیگر از کد هم داشته باشد: finally.

اگر این بند وجود داشته باشد، در تمام موارد اجرا می‌شود:

  • بعد از try، اگر اروری وجود نداشته باشد،
  • بعد از catch، اگر اروری وجود داشته باشد.

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

try {
   ... سعی در اجرای کد ...
} catch (err) {
   ... مدیریت ارورها ...
} finally {
   ... همیشه اجرا می‌شود ...
}

سعی کنید این کد را اجرا کنید:

try {
  alert( 'try' );
  if (confirm('ارور ایجاد کنیم؟')) BAD_CODE();
} catch (err) {
  alert( 'catch' );
} finally {
  alert( 'finally' );
}

این کد 2 راه برای اجرا دارد:

  1. اگر به سوال «ارور ایجاد کنیم؟» جواب «بله» دهید، سپس try -> catch -> finally.
  2. اگر شما «نه» بگویید، سپس try -> finally.

بند finally اغلب زمانی استفاده می‌شود که ما انجام کاری را شروع می‌کنیم و می‌خواهیم با هر نتیجه‌ای آن را به پایان برسانیم.

برای مثال، ما می‌خواهیم زمانی که یک تابع اعداد فیبوناچی fib(n) می‌گیرد را محاسبه کنیم. طبیعتا، ما می‌توانیم قبل از اینکه اجرا شود اندازه‌گیری را آغاز کنیم و سپس آن را تمام کنیم. اما اگر در حین فراخوانی تابع ارور ایجاد شود چه؟ به خصوص در کد پایین، پیاده‌سازی fib(n) به ازای اعداد منفی یا غیر صحیح یک ارور برمی‌گرداند.

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

اینجا finally تضمین می‌کند که زمان در هر دو وضعیت به درستی اندازه‌گیری می‌شود – در وضعیتی که اجرای fib موفقیت‌آمیز باشد و در وضعیتی که اروری درون آن باشد:

let num = +prompt("یک عدد مثبت وارد کنید.", 35)

let diff, result;

function fib(n) {
  if (n < 0 || Math.trunc(n) != n) {
    throw new Error("نباید منفی باشد، همچنین عدد صحیح قابل قبول است.");
  }
  return n <= 1 ? n : fib(n - 1) + fib(n - 2);
}

let start = Date.now();

try {
  result = fib(num);
} catch (err) {
  result = 0;
} finally {
  diff = Date.now() - start;
}

alert(result || "اروری رخ داد");

alert( `اجرای کد ${diff} میلی‌ثانیه طول کشید.` );

شما می‌توانید با اجرای کد بالا همراه با وارد کردن 35 درون prompt بررسی کنید – کد به صورت معمولی اجرا می‌شود، finally بعد از try. سپس 1- را وارد کنید – بلافاصله ارور ایجاد می‌شود و اجرای کد 0ms طول می‌کشد. هر دو اندازه‌گیری به درستی انجام شده‌اند.

به عبارتی دیگر، تابع می‌تواند با return یا throw به اتمام برسد، این موضوع مهم نیست. بند finally در هر دو مورد اجرا می‌شود.

متغیرهای درون try...catch...finally محلی هستند

لطفا در نظر داشته باشید که در کد بالا متغیرهای result و diff قبل از try...catch تعریف شده‌اند.

در غیر این صورت، اگر ما let را درون بلوک try تعریف می‌کردیم، این متغیر فقط درون همان بلوک قابل رویت بود.

بند finally و return

بند finally برای تمام خارج‌شدن‌ها از try...catch کار می‌کند. این موضوع شامل یک return واضح هم می‌شود.

در مثال پایین، یک return درون try وجود دارد. در این صورت، finally درست قبل از اینکه کنترل به کد بیرونی برگردد اجرا می‌شود.

function func() {

  try {
    return 1;

  } catch (err) {
    /* ... */
  } finally {
    alert( 'finally' );
  }
}

alert( func() ); // کار می‌کند و سپس این یکی finally درون alert اول
ساختار try...finally

ساختار try...finally، بدون بند catch، هم مفید است. ما زمانی که نمی‌خواهیم ارورها را مدیریت کنیم (می‌گذاریم رخ دهند) اما می‌خواهیم مطمئن باشیم فرایندهایی که شروع کردیم پایان می‌یابند آن را اعمال می‌کنیم.

function func() {
  // شروع انجام چیزی که به کامل شدن نیاز دارد (مثل اندازه‌گیری‌ها)
  try {
    // ...
  } finally {
    // کامل کردن آن حتی اگر همه چیز بمیرد
  }
}

در کد بالا، همیشه یک ارور از داخل try بیرون می‌آید چون catch وجود ندارد. اما قبل از اینکه جریان اجرای برنامه از تابع بیرون بیاید finally کار می‌کند.

catch گلوبال

مختص به محیط اجرا

اطلاعات این قسمت بخشی از جاوااسکریپت اصلی نیست.

بیایید فرض کنیم که بیرون از try...catch یک ارور مهلک رخ داده است و اسکریپت می‌میرد. مثلا یک ارور برنامه‌نویسی یا یک چیز وحشتناک دیگر.

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

درون مشخصات زبان راهی وجود ندارد اما محیط‌های اجرا معمولا راهی را فراهم می‌کنند چون این کار بسیار مفید است. برای مثال، Node.js برای این کار process.on("uncaughtException") را دارد. و در مرورگر ما می‌توانیم به ویژگی مخصوص window.onerror یک تابع اختصاص دهیم که در صورت رخ دادن یک ارور کنترل نشده اجرا شود.

The syntax:

window.onerror = function(message, url, line, col, error) {
  // ...
};
message
پیام ارور.
url
URL اسکریپتی که ارور در آنجا رخ داده است.
line، col
اعداد خط و ستون که ارور در آنجا رخ داده است.
error
شیء ارور.

برای مثال:

<script>
  window.onerror = function(message, url, line, col, error) {
    alert(`${message}\n At ${line}:${col} of ${url}`);
  };

  function readData() {
    badFunc(); // !اوه، یک جای کار می‌لنگد
  }

  readData();
</script>

معمولا نقش کنترل‌کننده گلوبال window.onerror این نیست که اجرای اسکریپت را ترمیم کند – این موضوع در صورتی که ارور برنامه‌نویسی وجود داشته باشد احتمالا غیر ممکن است اما فرستادن پیام ارور به توسعه‌دهندگان ممکن است.

همچنین سرویس‌های وب وجود دارند که رخدادنگاری ارور را برای چنین مواردی فراهم می‌کنند مانند https://errorception.com یا http://www.muscula.com.

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

  1. ما در سرویس ثبت نام می‌کنیم و از آن‌ها تکه‌ای از کد جاوااسکریپت (یا یک URL اسکریپت) برای اضافه کردن به صفحات دریافت می‌کنیم.
  2. آن کد جاوااسکریپت یک تابع window.onerror شخصی‌سازی شده را تنظیم می‌کند.
  3. زمانی که اروری رخ می‌دهد، این تابع درباره آن ارور، یک درخواست شبکه را به سرویس ارسال می‌کند.
  4. ما می‌توانیم وارد رابط وب سرویس شویم و ارورها را ببینیم.

خلاصه

ساختار try...catch مدیریت ارورهای زمان اجرا را ممکن می‌سازد. این ساختار به طور لفظی اجازه می‌دهد که اجرای کد را «امتحان کنیم (try)» و ارورهایی که ممکن است درون آن رخ بدهند را «بگیریم (catch)».

سینتکس آن:

try {
  // این کد را اجرا کن
} catch (err) {
  // اگر اروری رخ داد، سپس به اینجا بپر
  // شیء ارور است err
} finally {
  // این قسمت را انجام بده try/catch در هر صورت، بعد از
}

ممکن است قسمت catch یا finally وجود نداشته باشد پس ساختارهای کوتاه‌تر try...catch و try...finally هم معتبر هستند.

شیءهای ارور ویژگی‌های پایین را دارند:

  • message – پیام ارور که برای انسان قابل خواندن است.
  • name – رشته حاوی اسم ارور (اسم تابع سازنده ارور)
  • stack (استاندارد نیست، اما به خوبی پشتیبانی می‌شود) – پشته‌ای که در لحظه ایجاد ارور وجود دارد.

اگر شیء ارور نیاز نباشد، ما می‌توانیم با استفاده از catch { به جای catch (err) { آن را حذف کنیم.

همچنین می‌توانیم با استفاده از عملگر throw ارورهای خودمان را ایجاد کنیم. از لحاظ فنی، آرگومان throw می‌تواند هر چیزی باشد اما معمولا یک شیء ارور است که از کلاس درون‌ساخت Error ارث‌بری می‌کند. اطلاعات بیشتری درباره تعمیم دادن ارورها در فصل بعدی وجود دارد.

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

حتی اگر ما try...catch نداشته باشیم، اکثر محیط‌های اجرا به ما اجازه می‌دهند که یک کنترل‌کننده ارور «گلوبال» را برای گرفتن ارورهایی که «بیرون می‌افتند» بسازیم. در مرورگر window.onerror همان کنترل‌کننده است.

تمارین

اهمیت: 5

این دو قطعه کد را مقایسه کنید.

  1. کد اول از finally برای اجرای کد بعد از try...catch استفاده می‌کند:

    try {
      انجام کارها
    } catch (err) {
      مدیریت ارورها
    } finally {
      پاک سازی فضاری کاری
    }
  2. قطعه دوم پاک سازی را درست بعد از try...catch قرار می‌دهد:

    try {
      انجام کارها
    } catch (err) {
      مدیریت ارورها
    }
    
    پاک سازی فضای کاری

ما قطعا به پاک سازی بعد از کار نیاز داریم، مهم نیست که ارور وجود داشته باشد یا خیر.

آیا اینجا استفاده از finally برتری دارد یا هر دو قطعه کد یکسان هستند؟ اگر برتری وجود داشته باشد، سپس برای زمانی که این برتری مهم است یک مثال بزنید.

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

اگر یک «پرش به بیرون» از try...catch وجود داشته باشد، رفتار متفاوت است.

برای مثال، زمانی که یک return درون try...catch وجود دارد. بند finally در صورت هر گونه خارج شدن از try...catch کار می‌کند حتی با دستور return: درست بعد تمام شدن try...catch اما قبل از اینکه کد فراخوانی شده کنترل را به دست بگیرد.

function f() {
  try {
    alert('شروع');
    return "نتیجه";
  } catch (err) {
    /// ...
  } finally {
    alert('پاک سازی!');
  }
}

f(); // !پاک سازی

…یا زمانی که یک throw وجود داشته باشد، مثل اینجا:

function f() {
  try {
    alert('شروع');
    throw new Error("یک ارور");
  } catch (err) {
    // ...
    if("نمی‌توانی ارور را مدیریت کنی") {
      throw err;
    }

  } finally {
    alert('پاک سازی!')
  }
}

f(); // !پاک سازی

این finally است که در اینجا پاک سازی را تضمین می‌کند. اگر ما فقط کد را در انتهای f قرار دهیم، در این موقعیت‌ها اجرا نمی‌شود.

نقشه آموزش