Promiseها در جاوااسکریپت – بخش سوم

تا این‌جا Promiseها در جاوااسکریپت را معرفی کردیم. نحوه‌ی استفاده از آن را مورد بررسی قرار دادیم. گفتیم چه‌طور می‌شود Promise تعریف کرد و کتابخانه‌های دیگر را با این ویژگی هماهنگ کرد. یاد گرفتیم چطور می‌شود Promise را به کار گرفت. مفهوم زنجیربافی را شرح دادیم و با استفاده از مفاهیم شرح داده شده یک پروژه فرضی نیز شروع کردیم تا گام به گام پیش ببریم و تکمیل کنیم. در این بخش به مدیریت خطای Promiseها می‌پردازیم. اگر این سری مقاله را از ابتدا پیگیری نکرده‌اید پیشنهاد می‌کنم از بخش اول شروع کنید.

مدیریت خطای Promiseها

همان‌طور که قبلا دیدیم، متد then() دو آرگومان می‌گیرد: یکی برای موفقیت و یکی برای شکست. (یا به زبان Promiseها یکی برای تحقق (resolve) و یکی برای رد شدن (reject)):

get('story.json').then(function(response) {
  console.log("Hoooora!", response);
}, function(error) {
  console.log("Poooof!", error);
})

همچنین می‌توان از catch() نیز استفاده کرد:

get('story.json').then(function(response) {
  console.log("Hoooora!", response);
}).catch(function(error) {
  console.log("Poooof!", error);
})

catch() متد عجیب غریبی نیست. بلکه معادل then(undefined, func) است. البته صورت خواناتری دارد. دقت کنید که دو کد بالا مثل هم رفتار نمی‌کنند. بلکه کد دومی معادل کد زیر است:

get('story.json').then(function(response) {
  console.log("Hoooora!", response);
}).then(undefined, function(error) {
  console.log("Poooof!", error);
})

تفاوت دو کد بسیار ظریف است. اما در عین حال مهم نیز هست. وقتی یک Promise ریجکت می‌شود، این callback ریجکت در متد then() بعدی و یا متد catch() است که فراخوانی می‌شود. پس در مورد then(func1, func2) تنها یکی از آرگومان‌ها فراخوانی می‌شود اما در مورد then(func1).catch(func2) چون دو تابع در امتداد زنجیره قرار دارند، اگر func1 ریجکت شود، هر دوی func1 و func2 اجرا می‌شوند. به مثال زیر توجه کنید:

asyncThing1().then(function() {
  return asyncThing2();
}).then(function() {
  return asyncThing3();
}).catch(function(err) {
  return asyncRecovery1();
}).then(function() {
  return asyncThing4();
}, function(err) {
  return asyncRecovery2();
}).catch(function(err) {
  console.log("Don't worry about it");
}).then(function() {
  console.log("All done!");
})

جریان کد بالا بسیار شبیه به try/catch در جاوااسکریپت است. خطاهایی که در قسمت try اتفاق می‌افتند بلافاصله به بلوک catch پرت می‌شوند. بیایید کد بالا را به شکل فلوچارت رسم کنیم:

فلوچارت مربوط به کد مدیریت خطای Promiseها
شکل ۱ – فلوچارت مربوط به کد مدیریت خطا

همان‌طور که می‌بینید، اَعمال غیر همزمان، تا خط ۵، به صورت پشت سر هم اتفاق افتاده است. اگر هر کدام از این اعمال resolve شود، هر عمل غیر همزمان، عمل غیر همزمان دیگر را به نوبت فراخوانی می‌کند. (فلش‌های آبی در فلوچارت). چون در خط ۵ متد catch() را داریم، هر کجا که عملی reject شود، عمل غیر همزمان asyncRecovery1 فراخوانی می‌شود (فلش‌های قرمز). اگر همه‌ی اعمال تا asyncThing4 با موفقیت اجرا شوند، آرگومان اول متد then() در خط ۷ اجرا می‌شود که عمل غیر همزمان asyncThing4 را اجرا می‌کند. اگر به هر علتی سر از asyncRecovery1 درآوردیم، در صورت resolve شدن asyncRecovery1 باز asyncThing4 اجرا می‌شود. اما اگر این عمل غیر همزمان reject شود، آرگومان دوم متد then() خط ۷ اجرا می‌شود که همان asyncRecovery2 است. حالا در خط ۱۱ متد catch() را داریم. به این معنی که اگر هرکدام از اعمال غیر همزمان asyncThing4 یا asyncRecovery2 ریجکت شوند، این متد اجرا می‌شود و بعد از اجرای این متد then() نهایی در خط ۱۳ اجرا می‌شود. اگر هم هیچ‌کدام reject نشد، متد catch() اجرا نشده و یک راست به سراغ then() نهایی خواهیم رفت.

رفتار Promiseها با خطاهای کد

callbackهای خطا علاوه بر این‌که هنگام reject شدن Promise فراخوانی می‌شوند، به طور ضمنی هنگام وجود خطا در کد نیز فراخوانی می‌شوند. به کد زیر توجه کنید:

var jsonPromise = new Promise(function(resolve, reject) {
  // agar JSON e naa mo'tabari raa be JSON.parse 
  // bedahim, error midahad:
  resolve(JSON.parse("in yek JSON nist"));
});

jsonPromise.then(function(data) {
  // in ghesmat ejraa nemishavad:
  console.log("Huraaaa!", data);
}).catch(function(err) {
  // be jaye aan in ghesmat ejraa mishavad:
  console.log("Poooof!", err);
})

این موضوع نشان می‌دهد که بهتر است کدهای مربوط به Promise را در قسمت callback آن بنویسیم. در این صورت اگر خطایی در هر قسمت از کد رخ دهد، خطاگیری به شکل اتوماتیک انجام شده و نیازی به کار اضافه نخواهد بود. همین مسأله در خطاهایی که در callback متد then() رخ می‌دهد نیز صادق است:

get('/').then(JSON.parse).then(function() {
  // ejraa nemishavad chon, '/' yek safheye HTML ast, na JSON
  // pas JSON.parse khata midahad
  console.log("Huraaaa!", data);
}).catch(function(err) {
  // dar avaz in ghesmat ejraa mishavad:
  console.log("Poooof!", err);
})

مدیریت خطا در عمل

برگردیم به پروژه‌ای که تعریف کرده بودیم. می‌توانیم از catch() برای نشان دادن خطا به کاربر استفاده کنیم:

getJSON('story.json').then(function(story) {
  return getJSON(story.chapterUrls[0]);
}).then(function(chapter1) {
  addHtmlToPage(chapter1.html);
}).catch(function() {
  addTextToPage("Khata dar neshan dadane fasl!");
}).then(function() {
  document.querySelector('.spinner').style.display = 'none';
})

اگر گرفتن story.chapterUrls[0] دچار مشکل شود، (مثلا با خطای ۵۰۰ یا آفلاین بودن کاربر)، همه‌ی callbackهای مربوط به موفقیت ریجکت می‌شوند که شامل یک مورد در getJSON() است که تبدیل JSON را انجام می‌دهد. یکی هم مربوط به افزودن HTML به صفحه است. در عوض callback داخل catch() در خط ۵ اجرا می‌شود و “خطا در نشان دادن فصل!” را در صفحه چاپ می‌کند. همانند try/catch در جاوااسکریپت، بعد از گرفتن خطا، کدهایی که در ادامه می‌آیند، پردازش می‌شوند. بنابراین چرخانک همیشه مخفی می‌شود (با دستور خط ۸) و این دقیقا چیزی هست که می‌خواهیم اتفاق بیفتد. کد بالا، در واقع، نسخه‌ی غیر همزمان کد زیر است:

try {
  var story = getJSONSync('story.json');    //Sync == Hamzaman
  var chapter1 = getJSONSync(story.chapterUrls[0]);
  addHtmlToPage(chapter1.html);
}
catch (e) {
  addTextToPage("Khata dar neshan dadane fasl!");
}
document.querySelector('.spinner').style.display = 'none';

در این‌جا ممکن است بخواهید در هر مرحله از کار، مثلا به منظور رفع باگ، برای خودتان از خطا گزارش‌گیری کنید؛ بدون این‌که در روند خطاگیری فعلی تغییری ایجاد شود. در این صورت بعد از گرفتن خطا باید دوباره آن را پرت (throw) کنید. مثلا اگر بخواهیم در تابع getJSON() خطا را بگیریم و در عین حال عبارت “خطا در نشان دادن فصل!” نیز برای کاربر نشان داده شود می‌نویسیم:

function getJSON(url) {
  return get(url).then(JSON.parse).catch(function(err) {
    console.log("khata dar getJSON be khatere, ", url, err);
    throw err;
  });
}

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

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *