دوال المُعاملة للوعود .then
و .catch
و.finally
هي دوال غير متزامنة، دومًا.
فحتّى لو سويَ الوعد مباشرةً (أي سواءً أنُجز أو رُفض) فالشيفرة أسفل .then
و.catch
و.finally
ستُنفّذ حتّى قبل دوال المعاملة.
لاحِظ:
let promise = Promise.resolve(); promise.then(() => alert("promise done!")) // اكتمل الوعد alert("code finished"); // نرى هذا النص أولًا (انتهت الشيفرة)
لو شغّلته فسترى أولًا code finished
وبعدها ترى promise done!
.
هذا… غريب إذ أن الوعد أنجز قطعًا في البداية.
لماذا شُغّلت .then
بعدئذ؟ ما الّذي يحدث؟
طابور المهام السريعة
تطلب الدوال غير المتزامنة عملية إدارة مضبوطة. ولهذا تحدّد مواصفة اللغة طابورًا داخليًا باسم PromiseJobs
(غالبًا ما نسمّيه ”بطابور المهام السريعة“ Microtask Queue حسب مصطلح محرّك V8).
تقول المواصفة:
- الطابور بمبدأ ”أوّل من يدخل هو أوّل من يخرج“: المهام التي تُضاف أولًا تُنفّذ أولًا.
- لا يبدأ تنفيذ المهمة إلّا لو لم يكن هناك شيء آخر يعمل.
وبعبارة أبسط، فمتى جهز الوعد تُضاف دوال المعاملة .then/catch/finally
إلى الطابور، وتبقى هناك بلا تنفيذ. متى وجد محرّك جافاسكربت نفسه قد فرغ من الشيفرة الحالية، يأخذ مهمة من الطابور وينفّذها.
لهذا السبب نرى ”اكتملت الشيفرة“ في المثال أعلاه أولًا.
دوال معاملة الوعود تمرّ من الطابور الداخلي هذا دومًا.
لو كانت في الشيفرة سلسلة من .then/catch/finally
فستُنفّذ كلّ واحدة منها تنفيذًا غير متزامن. أي أنّ الأولى تُضاف إلى الطابور وتُنفّذ متى اكتمل تنفيذ الشيفرة الحالية وانتهت دوال المُعاملة الّتي أُضيفت إلى الطابور مسبقًا.
ولكن ماذا لو كان الترتيب يهمّنا؟ كيف نشغّل code finished
بعد promise done
؟
بسيطة، نضعها في الطابور باستعمال .then
:
Promise.resolve() .then(() => alert("promise done!")) .then(() => alert("code finished"));
هكذا صار الترتيب كما نريد.
الرفض غير المعالج
تذكر حدث unhandledrejection
من فصل التعامل مع الأخطاء في الوعود.
سنرى الآن كيف تعرف محرّكات جافاسكربت ما إن وُجدت حالة رفض لم يُتعامل معها، أم لا.
تحدث ”حالة الرفض لم يُتعامل معها“ حين لا يتعامل شيء مع خطأ أنتجه وعد في آخر طابور المهام السريعة.
عادةً لو كنّا نتوقّع حدوث خطأ نُضيف التابِع .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
.
لو بقينا نجهل طريقة عمل طابور المهام السريعة فسنتساءل: ”لماذا عملت دالة المُعاملة unhandledrejection
؟ الخطأ والتقطناه!“.
أمّا الآن فنعرف أنّه لا يُولّد unhandledrejection
إلّا حين انتهاء طابور المهام السريعة: فيفحص المحرّك الوعود وإن وجد حالة ”رفض“ في واحدة، يشغّل الحدث.
في المثال أعلاه، أضيفت .catch
وشغّلت من قِبل setTimeout
متأخرةً بعد حدوث unhandledrejection
لذا فإن ذلك لم يغيّر أي شيء.
خلاصة
التعامل مع الوعود دومًا يكون غير متزامن، إذ تمرّ إجراءات الوعود في طابور داخلي ”لمهام الوعود“ أو ما نسمّيه ”بطابور المهام السريعة“ (مصطلح المحرّك V8).
بهذا لا تُستدعى دوال المُعاملة .then/catch/finally
إلّا بعد اكتمال الشيفرة الحالية.
ولو أردنا أن نضمن تشغيل هذه الأسطر بعينها بعد .then/catch/finally
فيمكننا إضافتها إلى استدعاء .then
في السلسلة.
في معظم محركات جافاسكربت، بما في ذلك المتصفحات و Node.js، يرتبط مفهوم المهام السريعة ارتباطًا وثيقًا بـ "حلقة الأحداث" والمهام الكبيرة "macrotasks". نظرًا لأنها لا تملك علاقة مباشرة بالوعود، لذا فإننا شرحناها في جزء آخر من السلسلة التعليمية، في الفصل Event loop: microtasks and macrotasks.
ترجمة -وبتصرف- للفصل Microtasks من كتاب The JavaScript language
أفضل التعليقات
لا توجد أية تعليقات بعد
انضم إلى النقاش
يمكنك أن تنشر الآن وتسجل لاحقًا. إذا كان لديك حساب، فسجل الدخول الآن لتنشر باسم حسابك.