دليل تعلم جافاسكربت المهام السريعة Microtasks مقابل الوعد في تنفيذ المهام لاحقًا في جافاسكربت


صفا الفليج

دوال المُعاملة للوعود ‎.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 إلى الطابور، وتبقى هناك بلا تنفيذ. متى وجد محرّك جافاسكربت نفسه قد فرغ من الشيفرة الحالية، يأخذ مهمة من الطابور وينفّذها.

لهذا السبب نرى ”اكتملت الشيفرة“ في المثال أعلاه أولًا.

promiseQueue.png

دوال معاملة الوعود تمرّ من الطابور الداخلي هذا دومًا.

لو كانت في الشيفرة سلسلة من ‎.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





تفاعل الأعضاء


لا توجد أيّة تعليقات بعد



يجب أن تكون عضوًا لدينا لتتمكّن من التعليق

انشاء حساب جديد

يستغرق التسجيل بضع ثوان فقط


سجّل حسابًا جديدًا

تسجيل الدخول

تملك حسابا مسجّلا بالفعل؟


سجّل دخولك الآن