هندلرهای (handler) پرامیس .then
/.catch
/.finally
همواره ناهمگام هستند.
حتی زمانی که یک پرامیس در آن واحد به سر انجام رسیده, کد هایی که در خطوط زیرین .then
/.catch
/.finally
هستند هنوز پیش از این هندلرها (handler) اجرا می شوند.
یک نمونه:
let promise = Promise.resolve();
promise.then(() => alert("promise done!"));
alert("code finished"); // ابتدا این هشدار نمایان می شود
اگر این کد را اجرا کنید, عبارت code finished
را در ابتدا و سپس promise done!
را میبینید.
این عجیب است, زیرا پرامیس قطعا از پیش به انجام رسیده است.
چرا .then
بعدتر اجرا شد؟ چه رخ میدهد؟
صف Microtasks
کار های ناهمگام نیازمند مدیریت درست هستند. به همین سبب، استاندارد ECMA یک صف داخلی به نام PromiseJobs
مشخص میکند که بیشتر با نام “microtask queue” از آن یاد می شود (اصطلاح V8).
همانطور که در خصوصیات زبان یاد شده:
- صف first-in-first-out است: کارهایی که نخست وارد صف شده اند نخست اجرا می شوند.
- اجرای یک کار تنها زمانی شروع می شود که چیز دیگری در حال اجرا نباشد.
یا، به عبارت ساده تر، زمانی که یک پرامیس آماده است، مدیر های .then/catch/finally
آن درون صف قرار داده می شوند؛ آنها هنوز اجرا نشده اند. زمانی که موتور جاوااسکریپت از کد فعلی رها می شود، یک کار (تسک) از صف میگیرد و آن را اجرا میکند.
به همین دلیل عبارت “code finished” در نمونه بالا نخست نمایان می شود.
هندلرهای (handler) پرامیس همواره از درون این صف داخلی می گذرند.
اگر زنجیره ای با چندین .then/catch/finally
باشد، آنگاه هر یک از آنها به صورت ناهمگام اجرا می شود. بدان صورت که، ابتدا وارد صف می شود، سپس زمانی که کد فعلی تمام شده و هندلرهای (handler) پیشین صف شده به پایان رسیده اند، اجرا می شود.
اگر ترتیب برای ما اهمیت داشت چه? چگونه می توانیم code finished
را پیش از promise done
نمایان کنیم؟
به سادگی، فقط با استفاده از .then
درون صف قرارش بده:
Promise.resolve()
.then(() => alert("promise done!"))
.then(() => alert("code finished"));
حالا ترتیب در نظر گرفته شده.
rejection مدیریت نشده
ایونت unhandledrejection
را از مقاله مدیریت ارورها با promiseها به یاد دارید؟
حال میتوانیم به دقت ببینیم که جاوااسکریپت چگونه پی میبرد که رد شدن مدیریت نشده ای پیش آمده.
یک “rejection مدیریت نشده” زمانی پیش می آید که یک خطای پرامیس در پایان صف خرده کار مدیریت نشده باشد.
معمولا، اگر منتظر خطایی هستیم، .catch
را به زنجیره پرامیس می افزاییم تا آن را مدیریت کند:
let promise = Promise.reject(new Error("Promise Failed!"));
promise.catch(err => alert('caught'));
// اجرا نمی شود: خطا مدیریت شد
window.addEventListener('unhandledrejection', event => alert(event.reason));
اما اگر فراموش کنیم که .catch
را بیافزاییم، آنگاه، پس از اینکه صف خرده کار خالی شد، موتور جاوااسکریپت، ایونت را فراخوانی میکند:
let promise = Promise.reject(new Error("Promise Failed!"));
// Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));
چه می شود اگر خطا را بعدتر مدیریت کنیم؟ مثل این کد:
let promise = Promise.reject(new Error("Promise Failed!"));
setTimeout(() => promise.catch(err => alert('caught')), 1000);
// Error: Promise Failed!
window.addEventListener('unhandledrejection', event => alert(event.reason));
حال، اگر اجرایش کنیم، عبارت Promise Failed!
را نخست و سپس عبارت caught
را مشاهده خواهیم کرد.
اگر ما درباره صف Microtasks نمیدانستیم، می توانستیم شگفت زده شویم: “چرا هندلر unhandledrejection
اجرا شد؟ ما یقینا خطا را گرفتیم و مدیریت کردیم!”
اما حال متوجه می شویم که unhandledrejection
زمانی ایجاد می شود که Microtask کار تمام شده: موتور جاوااسکریپت پرامیس ها را بررسی میکند و، اگر هر یک از آنها در وضعیت “rejected” باشد، آنگاه ایونت فراخوانی می شود.
در نمونه بالا، .catch
افزوده شده توسط setTimeout
هم فراخوانی می شود. اما بعدتر فراخوانی می شود، پس از اینکه دیگر unhandledrejection
رخ داده، پس چیزی را تغییر نمیدهد.
چکیده
مدیریت پرامیس همواره ناهمگام است، همانطور که تمام عملیات پرامیس از درون صف داخلی “promise jobs” می گذرند، همچنین با عنوان “microtask queue” از آن یاد می شود (اصطلاح V8).
پس هندلر های .then/catch/finally
همواره پس از به پایان رسیدن کد فعلی فراخوانی می شوند.
اگر نیاز داریم که تضمین کنیم یک تکه کد پس از .then/catch/finally
اجرا می شود، می توانیم به یک فراخوانی زنجیره ای .then
آن را بیافزاییم.
در بیشتر موتور های جاوااسکریپت، شامل مرورگر ها و Node.js، مفهوم Microtasks به دقت با “event loop” و “macrotasks” در هم تنیده شده. از آنجایی که این دو رابطه مستقیمی با پرامیس ها ندارند، در بخش دیگری از این دوره به آنها پرداخته شده، در مقاله Event loop: microtasks و macrotasks.