Skip to content

11. البرمجة القائمة على الأحداث والوظائف غير المتزامنة

Image

الدالة غير المتزامنة هي دالة يبدأ تنفيذها ولكن لا يتم انتظار نتيجتها. عند اكتمال التنفيذ، تصدر الدالة غير المتزامنة حدثًا وترسل نتيجتها عبر هذا الحدث.

هذا النمط من التشغيل مناسب تمامًا للتنفيذ داخل متصفح الويب. في الواقع، التطبيق الذي يعمل داخل المتصفح هو تطبيق مدفوع بالأحداث: يتفاعل التطبيق مع الأحداث، التي يطلقها المستخدم في المقام الأول (النقرات، وحركات الماوس، وإدخال النص، وما إلى ذلك). تتفاعل تطبيقات JavaScript التي تعمل داخل متصفح مع الخدمات الخارجية عبر بروتوكول HTTP. وظائف HTTP الأصلية في JavaScript غير متزامنة: يتم بدء تشغيلها، ويتم الإشارة إلى استلام استجابة من الخدمة الخارجية المطلوبة بواسطة حدث يُضاف إلى مجموعة الأحداث التي يديرها التطبيق.

سيتم تنفيذ البرامج النصية التالية بواسطة [node.js] وليس بواسطة متصفح. كما أن [node.js] لديه نموذج تنفيذ مدفوع بالأحداث:

  • يستخدم [node.js]، مثل المتصفح، حلقة أحداث لتنفيذ البرنامج النصي؛
  • يُعد تنفيذ الكود الرئيسي للنص البرمجي هو الحدث الأول الذي يتم تنفيذه؛
  • إذا كان هذا الكود الرئيسي قد أطلق مهام غير متزامنة، يستمر التنفيذ حتى تكتمل هذه المهام غير المتزامنة. تصدر هذه المهام حدثًا عند انتهائها. يتم وضع هذه الأحداث في قائمة انتظار في حلقة الأحداث؛
  • يجب أن يشترك البرنامج النصي الرئيسي في هذه الأحداث إذا أراد استرداد نتائج الإجراءات غير المتزامنة؛
  • لا ينتهي البرنامج النصي حتى تتم معالجة جميع الأحداث التي أطلقها؛

11.1. البرنامج النصي [async-01]

يوضح البرنامج النصي التالي سلوك برنامج نصي يحتوي على إجراء غير متزامن.


'use strict';
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
 
// setTimeout arms a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[fin de l'action asynchrone setTimeout],", heure(débutScript));
}, 1000)
 
// will be displayed before the internal timer function msg
console.log("[fin du code principal du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // is it necessary to calculate a duration?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // format time + duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}
  • السطر 4: نستورد مكتبة [moment] لتنسيق التواريخ (السطر 27)؛
  • السطر 5: نستورد مكتبة [sprintf-js] لتنسيق المدد الزمنية (السطر 34)؛
  • السطر 8: نسجل وقت بدء البرنامج النصي؛
  • السطر 9: نعرضه باستخدام طريقة [time] في الأسطر 20–34؛
  • الأسطر 14-17: تحتوي الدالة [setTimeout] على معلمتين (f، duration): f هي دالة يتم تنفيذها عند انقضاء [duration] مللي ثانية؛
  • السطر 14: عند تشغيل البرنامج النصي، يتم تنفيذ دالة [setTimeout]:
    • السطر 17: يتم تشغيل مؤقت مدته 1000 مللي ثانية ويبدأ العد التنازلي حتى يصل في النهاية إلى 0. تكتمل دالة [setTimeout] بمجرد تهيئة المؤقت وبدء العد التنازلي. وهي لا تنتظر انتهاء العد التنازلي. وتُرجع رقم معرف للمؤقت المستخدم، وينتقل التنفيذ إلى التعليمات التالية، السطر 20. هنا، لا يتم استخدام نتيجة [setTimeout]؛
  • السطر 16: سيتم عرض هذه الرسالة في نهاية تأخير 1000 مللي ثانية لدالة [setTimeout]؛
  • السطران 15-16: سيتم تنفيذ الدالة f، وهي المعلمة الأولى لدالة [setTimeout]، في نهاية فترة التأخير البالغة 1000 مللي ثانية. ثم سيتم عرض الرسالة الموجودة في السطر 16؛
  • السطر 20: سيتم عرض هذه الرسالة قبل الرسالة الموجودة في السطر 16؛

وظيفة الوقت:

  • السطر 23: تقبل الدالة معلمة اختيارية [timeوهي وقت بدء العملية التي يجب عرض مدتها؛
  • الأسطر 25-27: يتم حساب الوقت الحالي وتنسيقه؛
  • السطر 29: إذا كانت المعلمة [start] موجودة، فيجب حساب المدة؛
  • السطر 30: مدة العملية. وينتج عن ذلك عدد من المللي ثانية؛
  • السطور 31-32: يتم تقسيم هذا العدد من المللي ثانية إلى ثوانٍ ومللي ثانية؛
  • السطر 34: تُضاف المدة إلى الوقت؛

التنفيذ


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-01.js"
[début du script], heure=09:26:40:238
[fin du code principal du script], heure=09:26:40:246, durée= 0 seconde(s) et 11 millisecondes
[fin de l'action asynchrone setTimeout], heure=09:26:41:249, durée= 1 seconde(s) et 14 millisecondes
 
[Done] exited with code=0 in 1.672 seconds
  • السطر 4، يمكننا أن نرى أن الإجراء غير المتزامن [setTimeout] انتهى بعد حوالي ثانية واحدة من انتهاء الكود الرئيسي للنص البرمجي؛
  • السطر 6: الوقت المعروض في السطر 3 هو الوقت الذي ينتهي فيه الكود الرئيسي. إذا كان الكود الرئيسي قد بدأ مهام غير متزامنة، فلن يكتمل البرنامج النصي حتى يتم تنفيذ جميع المهام غير المتزامنة. المدة المعروضة في السطر 6 هي إجمالي وقت تنفيذ البرنامج النصي (الكود الرئيسي + المهام غير المتزامنة)؛

ستسمح لنا دالة [setTimeout] بمحاكاة المهام غير المتزامنة في بيئة [node.js]. في الواقع، تتصرف دالة [setTimeout] كأنها مهمة غير متزامنة:

  • فهي تُرجع نتيجةً فوراً — في هذه الحالة، معرّف مؤقت — باستخدام آلية الدالة القياسية (return
  • قد تعيد لاحقًا (وهو ما لم يحدث بعد في الحالة أعلاه) نتائج أخرى عبر أحداث تتم معالجتها بعد ذلك بواسطة حلقة أحداث [node.js]؛
  • في معظم الحالات التالية، سيكون هناك حدثان من هذا النوع:
    • حدث يمكن تسميته [success]، والذي سيتم إصداره بواسطة المهمة غير المتزامنة التي أكملت مهمتها بنجاح. ترتبط قطعة من البيانات — نتيجة المهمة — بالحدث الذي تم إصداره؛
    • حدث يمكن تسميته [failure]، والذي يتم إصداره بواسطة المهمة غير المتزامنة التي فشلت في إكمال مهمتها. ترتبط قطعة من البيانات — عادةً كائن يصف الخطأ — بالحدث الذي تم إصداره. قد تكون الأخطاء المحتملة في مهمة إنترنت غير متزامنة، على سبيل المثال، "الشبكة غير متاحة"، "جهاز الخادم غير موجود"، "تجاوز مهلة الانتظار"، ...
  • يمكن للكود الرئيسي الذي أطلق المهمة غير المتزامنة الاشتراك في الأحداث التي من المحتمل أن تصدرها هذه المهمة. وعندما يصدر أحد هذه الأحداث، يتم إخطار الكود الرئيسي ويمكنه تشغيل وظيفة محددة مصممة للتعامل مع الحدث. تتلقى هذه الوظيفة كمعلمة البيانات التي ترتبط بها المهمة غير المتزامنة المرتبطة بالحدث الصادر؛

11.2. النص البرمجي [async-02]

في هذا البرنامج النصي، ستصدر الدالة غير المتزامنة [setTimeout] أحداثًا لتوصيل البيانات إلى الكود الذي اشترك فيها.

يتطلب الوصول إلى أحداث [node.js] مكتبات إضافية. نختار مكتبة [events]، التي نقوم بتثبيتها باستخدام [npm]:

Image

النص البرمجي [async-02] هو كما يلي:


'use strict';
 
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
 
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // an event is issued to indicate that a result is available
  eventEmitter.emit("timer1Success", { success: 4 });
  // another event is issued to indicate that another result is available
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
 
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
 
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});
 
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du code principal du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  // is it necessary to calculate a duration?
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // format time + duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

تعليقات

  • السطر 9: نستورد فئة [EventEmitter] من مكتبة [events]. هذا جديد: حتى الآن، كنا نستورد فقط كائنات ووظائف حرفية؛
  • السطر 15: نقوم بإنشاء مُصدر أحداث [node.js] عن طريق إنشاء مثيل لفئة [EventEmitter] باستخدام الكلمة الرئيسية [new]؛
  • الأسطر 20–27: الدالة غير المتزامنة [setTimeout]. ستصدر حدثين عند تنفيذها:
    • السطر 24، حدث [timer1Success] مع الكائن {success: 4} كقيمة مرتبطة به؛
    • السطر 26، حدث [timer1Failure] مع الكائن {failure: 6} كقيمة مرتبطة به؛
    • يمكن للدالة غير المتزامنة أن تصدر أي عدد تريده من الأحداث. ذكرنا سابقًا أنها غالبًا ما تصدر أحد الحدثين [success، failure]، وليس كليهما كما نفعل هنا؛
  • السطر 20: يتم تنفيذ [setTimeout] بشكل فوري: يتم ضبط مؤقت وإرجاع معرّفه إلى الكود المستدعي. سيتم إصدار الأحداث لاحقًا، في هذه الحالة بعد ثانية واحدة؛
  • لا فائدة من تشغيل الأحداث إذا لم يكن هناك كود للتعامل معها عند حدوثها. لهذا السبب يجب أن يشترك الكود الرئيسي في كلا الحدثين [timer1Success، timer1Failure] إذا أراد التعامل معهما، وتحديدًا لاسترداد البيانات المرتبطة بهذه الأحداث؛
  • الأسطر 30–32: يقوم الكود الرئيسي بالاشتراك في حدث [timer1Success]. وعندما يقوم مستمع الأحداث [node.js] بمعالجة هذا الحدث، فإنه سيستدعي الدالة التي تمثل المعلمة الثانية لطريقة [eventEmitter.on]، ممرراً إليها البيانات (المسماة هنا [result]) المرتبطة بحدث [timer1Success]؛
  • السطر 31: ستعرض دالة معالج الحدث JSON للبيانات المرتبطة بالحدث بالإضافة إلى الوقت الحالي؛
  • الأسطر 35–37: باستخدام كود مشابه، يشترك الكود الرئيسي في حدث [timer1Failure]؛
  • الاشتراك في حدث (المعلمة الأولى) لا يؤدي إلى تنفيذ كود دالة [callback] (المعلمة الثانية) على الفور. لن يتم تنفيذ هذه الدالة إلا بعد وقوع الحدث؛
  • السطر 40: انتهى كود البرنامج النصي الرئيسي، لكن البرنامج النصي نفسه لم ينتهِ بعد، لأن الكود الرئيسي أطلق مهمة غير متزامنة. لن ينتهي البرنامج النصي بأكمله حتى تكتمل هذه المهمة غير المتزامنة؛

هذا ما تظهره النتائج:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-02.js"
[début du script], heure=09:34:58:909
[fin du code principal du script], heure=09:34:58:916, durée= 0 seconde(s) et 10 millisecondes
[setTimeout, fin du timer d'1 s], heure=09:34:59:929, durée= 1 seconde(s) et 23 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=09:34:59:931, durée= 1 seconde(s) et 25 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=09:34:59:932, durée= 1 seconde(s) et 26 millisecondes, via l'événement [timer1Failure]
 
[Done] exited with code=0 in 1.627 seconds
 
  • السطر 3: نهاية الكود الرئيسي بعد 10 مللي ثانية من بدء تشغيل البرنامج النصي؛
  • السطر 4: بداية الوظيفة المُغلفة في مؤقت 1000 مللي ثانية، بعد حوالي ثانية واحدة من بدء البرنامج النصي؛
  • السطر 5: معالجة الحدث [‘timer1Success’]، بعد 2 مللي ثانية؛
  • السطر 6: معالجة الحدث [‘timer1Failure’]، بعد 1 مللي ثانية من الحدث [‘timer1Success’]؛
  • السطر 8: نهاية البرنامج النصي العام بمدة إجمالية تبلغ 1.627 ثانية؛

11.3. النص البرمجي [async-03]

يوضح البرنامج النصي التالي جانبًا آخر من حلقة أحداث [node.js]:

  • تقوم الحلقة بتنفيذ الأحداث واحدًا تلو الآخر، بشكل عام حسب ترتيب وصولها. تقوم بعض أنظمة التشغيل بتعيين أولويات للأحداث، والتي يتم معالجتها بعد ذلك حسب ترتيب الأولوية بدلاً من ترتيب الوصول؛
  • تنفذ الحلقة حدثًا واحدًا فقط في كل مرة. ولا تتم معالجة الحدث التالي إلا بعد انتهاء الحدث السابق. في نظام يعتمد على الأحداث، يجب عليك بالتالي تجنب كتابة كود يستحوذ على المعالج لفترات طويلة، حيث لن تتم معالجة الأحداث عند حدوثها بل لاحقًا عندما تصل إليها حلقة الأحداث. وهذا يؤدي إلى تطبيق غير "سريع الاستجابة" بشكل كبير؛

يوضح البرنامج النصي [async-03] مثالاً على هذه الظاهرة:


'use strict';
 
// asynchronous functions can return a result by emitting an event
// the main code can retrieve these results by subscribing to the events issued
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
import EventEmitter from 'events';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
// an event transmitter
const eventEmitter = new EventEmitter();
 
// setTimeout sets a 1000 ms timer (2nd parameter) and immediately returns the timer number
// when the timer has run out of 1000 ms, it emits an event which is queued by the runtime
// when the event is processed by the runtime, the function (1st parameter) is executed
setTimeout(function () {
  // this code will be executed when the timer reaches value 0
  console.log("[setTimeout, fin du timer d'1 s],", heure(débutScript));
  // an event is issued to indicate that a result is available
  eventEmitter.emit("timer1Success", { success: 4 });
  // another event is issued to indicate that another result is available
  eventEmitter.emit("timer1Failure", { failure: 6 });
}, 1000)
 
// subscribe to evt [timer1Success]
eventEmitter.on('timer1Success', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Success]", result, heure(débutScript)));
});
 
// subscribe to evt [timer1Failure]
eventEmitter.on('timer1Failure', (result) => {
  console.log(sprintf("la fonction asynchrone du timer a rendu le résultat [%j], %s, via l'événement [timer1Failure]", result, heure(débutScript)));
});

// slightly intensive synchronous code that prevented the main code from completing before the end of [timer1]
for (let i = 0; i < 1000000; i++) {
  for (let j = 0; j < 10000; j++) {
    i + i ^ 2 + i ^ 3;
  }
}
 
// will be displayed before the msg of evts issued by the function associated with [timer1]
console.log("[fin du script],", heure(débutScript));
 
// time and duration display utility
function heure(début) {
 ...
}

تعليقات

  • هذا الكود مأخوذ من المثال السابق [async-02الذي تمت إضافة الأسطر 39–44 إليه؛
  • الأسطر 20–27: تمت برمجة الدالة [setTimeout] لتنفيذ دالة داخلية غير متزامنة بعد تأخير مدته ثانية واحدة. بعد انقضاء هذه الثانية، لا يتم تنفيذ الدالة غير المتزامنة للمؤقت على الفور: يتم وضع حدث في حلقة التنفيذ لطلبها. إذا كانت حلقة التنفيذ مشغولة بمعالجة حدث آخر، فسيتعين على تنفيذ الدالة غير المتزامنة للمؤقت الانتظار؛
  • الأسطر 20–27: بمجرد أن تضبط دالة [setTimeout] مؤقتها لتأخير مدته ثانية واحدة، فإنها تحرر المعالج وتعيد التحكم إلى الكود المستدعي. يستمر الكود المستدعي بالأسطر 30–37، وهي اشتراكات في الأحداث ووقت تنفيذها ضئيل؛
  • يستمر الكود الرئيسي بالسطور 40-44، التي تشكل حلقة من 1,010 تكرار. سيتم تنفيذ هذا الكود عندما يطلق المؤقت حدث "نهاية تأخير ثانية واحدة". يتم بعد ذلك وضع هذا الحدث في حلقة الأحداث، ولكن يجب أن ينتظر حتى ينتهي تنفيذ الكود الرئيسي للنص البرمجي ليتم معالجته؛
  • السطر 47: نهاية الكود الرئيسي للنص البرمجي. وبعد ظهور هذه الشاشة النهائية، يمكن معالجة حدث انتهاء المؤقت وتنفيذ الوظيفة غير المتزامنة الداخلية لـ [setTimeout]؛

ينتج البرنامج النصي النتائج التالية:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-03.js"
[début du script], heure=08:55:02:665
[fin du code principal du script], heure=08:55:11:789, durée= 9 seconde(s) et 131 millisecondes
[setTimeout, fin du timer d'1 s], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes
la fonction asynchrone du timer a rendu le résultat [{"success":4}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Success]
la fonction asynchrone du timer a rendu le résultat [{"failure":6}], heure=08:55:11:794, durée= 9 seconde(s) et 136 millisecondes, via l'événement [timer1Failure]
 
[Done] exited with code=0 in 9.796 seconds

تعليقات

  • السطر 3: نرى أن تنفيذ الكود الرئيسي للبرنامج النصي استغرق 9 ثوانٍ. تم وضع أي أحداث وقعت خلال هذه الفترة في قائمة انتظار حلقة الأحداث؛
  • السطر 4: نرى أن حدث [timer end] تمت معالجته بعد 5 مللي ثانية من انتهاء الكود الرئيسي. تم إصداره بعد حوالي ثانية واحدة من بدء البرنامج النصي، لكنه اضطر إلى الانتظار لمدة 8 ثوانٍ إضافية حتى تمت معالجته أخيرًا؛

الاستنتاج الرئيسي من هذا المثال هو أنه في نظام يعتمد على الأحداث، يجب ألا يشغل الكود المعالج لفترة طويلة. إذا كان لديك كود متزامن يستغرق وقتًا طويلاً للتنفيذ، فيجب أن تجد طريقة لتقسيمه إلى مهام غير متزامنة أقصر تشير إلى اكتمالها بواسطة حدث.

11.4. البرنامج النصي [async-04]

يوضح البرنامج النصي [async-04] آلية أخرى، تسمى [Promise]، وهي وعد بنتيجة. تتجنب هذه الآلية الحاجة إلى التعامل بشكل صريح مع أحداث [node.js]. يتم التعامل معها بشكل ضمني، وبالتالي يمكن للمطور تجاهل وجود هذه الأحداث. ومع ذلك، فإن فهمها سيساعد المطور على فهم كيفية عمل [Promise] بشكل أفضل، وهو أمر معقد للوهلة الأولى.

نوع [Promise] هو فئة في JavaScript. يقبل منشئها دالة غير متزامنة كمعلمة، ويمرر إليها معلمتين يُطلق عليهما تقليديًا [resolve] و [reject]. يمكن أن يكون لهما أسماء مختلفة؛

1
2
3
4
5
6
7
const promise=new Promise(function(resolve, reject){
     // an asynchronous task is launched
    
     // if successful: call resolve(result) where [result] is the result of the asynchronous task;
     // if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to events issued by the [Promise] asynchronous task
  • يقوم منشئ [Promise] بأمرين:
    • إنه ينشئ حدثًا لتشغيل تنفيذ الدالة [function(resolve, reject)] التي تم تمريرها إليه كمعلمة، ولكنه لا ينتظر نتيجتها ويقوم على الفور بإرجاع كائن [Promise] إلى الكود المستدعي. يمكن أن يكون لهذا الكائن أربع حالات:
      • [pending]: الإجراء غير المتزامن الذي أعاد [Promise] لم يكتمل بعد؛
      • [fulfilled]: اكتمل الإجراء غير المتزامن الذي أعاد [Promise] بنجاح؛
      • [rejected]: فشل الإجراء غير المتزامن الذي أعاد [Promise]؛
      • [settled]: اكتمل الإجراء غير المتزامن الذي أعاد [Promise]؛

عندما يعرض المنشئ نتيجته، يكون كائن [Promise] الذي تم إنشاؤه في حالة [pending]، في انتظار نتائج الدالة غير المتزامنة؛

  • (تابع)
    • يتم تشغيل المهمة غير المتزامنة الواردة في الأسطر 2-5 على الفور. وغالبًا ما تكون المهام غير المتزامنة مهام إدخال/إخراج تنقسم على النحو التالي:
      1. تنفيذ كود متزامن لبدء عملية الإدخال/الإخراج مع مكون آخر، مثل خادم بعيد؛
      2. الانتظار للحصول على استجابة من ذلك المكون؛
      3. معالجة هذا الرد؛

المرحلة 2 — انتظار المكون الخارجي — هي الأكثر استهلاكًا للموارد. بدلاً من الانتظار:

  • سيتم الإشارة إلى استلام البيانات المطلوبة من المكون الخارجي بواسطة حدث؛
  • في الكود المتزامن الذي يتبع المرحلة 1 (السطر 7 من كود المثال)، سنشترك في هذا الحدث ثم، في مرحلة ما، نعود إلى حلقة أحداث [node.js]. بعد ذلك، سيتم معالجة الحدث التالي في قائمة الأحداث المعلقة؛
  • خلال المرحلة 2، يحدث تنفيذ متوازي ولكن على أجهزة مختلفة:
    • المعالج الخاص بحلقة الأحداث؛
    • مكون خارجي (قرص، قاعدة بيانات، خادم بعيد) لاسترداد البيانات المطلوبة؛
  • في نهاية المرحلة 2، بمجرد حصول عملية الإدخال/الإخراج على البيانات المطلوبة، سيتم تشغيل حدث للإشارة إلى أن نتيجة الإدخال/الإخراج متاحة. سينضم هذا الحدث بعد ذلك إلى الأحداث الأخرى في قائمة انتظار الأحداث؛
  • وعندما يحين دوره، سيتم معالجته. ثم سيتم تنفيذ الدالة المرتبطة بهذا الحدث (السطر 7 من كود المثال)؛

يساعد هذا النمط من التشغيل على تجنب فترات التعطل: الحالة التي ينتظر فيها المعالج استجابة من جهاز أبطأ منه؛

  • (تابع)
    • بمجرد إطلاق المهمة غير المتزامنة في السطرين 2 و 5 وإكمال عملها، يمكنها إرجاع نتيجة إلى الكود المستدعي باستخدام الدالتين [resolve, reject] اللتين مررهما منشئ [Promise] إليها كمعلمات. وتتمثل القاعدة في ما يلي:
      • تشير المهمة غير المتزامنة إلى النجاح عبر [resolve(result)]. وهذا يعادل إضافة حدث إلى حلقة أحداث [node.js] يمكن تسميته [resolved]، مع [result] كبيانات مرتبطة؛
      • تشير المهمة غير المتزامنة إلى الفشل عبر [reject(error)]. وهذا يعادل إضافة حدث إلى حلقة أحداث [node.js] يمكن تسميته [rejected]، مع [error] كبيانات مرتبطة به — وعادةً ما يكون كائنًا يوضح تفاصيل الخطأ الذي حدث؛
      • لذلك يجب أن يشترك الكود المستدعي في هذين الحدثين ليتم إخطاره عندما تكون نتيجة الدالة غير المتزامنة متاحة؛

بعد انتهاء تنفيذ المهمة غير المتزامنة المُغلفة في [Promise]، تتغير حالة كائن [promise] الذي يتم إرجاعه بواسطة مُنشئ [Promise(…)]:

  • يغير الحدث [resolved] حالته من [pending] إلى [resolved]؛
  • يغير الحدث [rejected] حالته من [pending] إلى [rejected]؛

يتم الاشتراك في أحداث [resolved] و [rejected] للمهمة غير المتزامنة باستخدام طرق فئة [Promise] بالصيغة التالية:

promise.then(f1).catch(f2).finally(f3);

حيث:

  • f1 هي دالة يتم تنفيذها عندما تتغير حالة [promise] من [pending] إلى [resolved]، أي عندما تنجز المهمة غير المتزامنة عملها بنجاح. تتلقى الدالة القيمة [result] كمعلمة، يتم تمريرها بواسطة عبارة [resolve(result)] الخاصة بالمهمة غير المتزامنة؛
  • f2 هي دالة يتم تنفيذها عندما تتغير حالة [promise] من [pending] إلى [rejected]، أي عندما تفشل المهمة غير المتزامنة في إكمال عملها. تتلقى الدالة القيمة [error] كمعلمة، يتم تمريرها بواسطة عبارة [reject(error)] الخاصة بالمهمة غير المتزامنة؛
  • f3 هي دالة يتم تنفيذها بعد تنفيذ طريقتي [then] أو [catch]، لذا فهي تُنفَّذ دائمًا. ولا تتلقى أي معلمات؛

تخفي هذه الصيغة تمامًا الأحداث التي نشترك فيها. ومع ذلك، فهي اشتراك، ومثل المثال السابق، لا تنفذ الدوال [f1, f2, f3] على الفور. سيتم تنفيذ هذه الدوال — أو عدم تنفيذها — عند حدوث أحد الأحداث [resolved, rejected] التي نشترك فيها.

يوضح البرنامج النصي [async-04] هذه الآلية:


'use strict';
 
// it is possible to obtain the results (success, failure) of an asynchronous function
// without explicitly using events thanks to the [Promise] class
// this class implicitly uses events, but these are not visible in the code
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure(débutScript));
 
// definition of an asynchronous task using a promise [Promise]
// the asynchronous task is the [Promise] constructor parameter
const débutPromise1 = moment(Date.now());
const promise1 = new Promise(function (resolve) {
  // log
  console.log("[début fonction asynchrone de promise1],", heure(débutPromise1));
  // asynchronous code
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise1],", heure(débutPromise1));
    // the asynchronous task returns a result with the [resolve] function
    // the promise is fulfilled
    resolve('[réussite]');
  }, 1000)
});
 
// we can know the result of the promise [promise1]
// when it has been resolved or rejected
// the following instruction is a subscription to the [resolved] event via the [then] method
// and to the [rejected] event via the [catch] method
// the [finally] method is executed whether after a then or a catch
promise1.then(result => {
  // promise success stories [evt resolved]
  console.log(sprintf("[promise1.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // error case [evt rejected]
  console.log(sprintf("[promise1.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executed in all cases
  console.log("[promise1.finally]", heure(débutPromise1));
});
 
// definition of an asynchronous task using a promise [Promise]
const débutPromise2 = moment(Date.now());
const promise2 = new Promise(function (resolve, reject) {
  // log
  console.log("[début fonction asynchrone de promise2],", heure(débutPromise1));
  // asynchronous task
  setTimeout(function () {
    console.log("[fin fonction asynchrone de promise2],", heure(débutPromise2));
    // the asynchronous task returns a result with the [reject] function
    // the promise is lost
    reject('[échec]');
  }, 2000)
});
 
// we can know the result of the promise [promise2]
// when it has been resolved or rejected
promise2.then(result => {
  // promise success stories [evt resolved]
  console.log(sprintf("[promise2.then], %s, result=%s", heure(débutPromise2), result));
}).catch(result => {
  // error case [evt rejected]
  console.log(sprintf("[promise2.catch], %s, result=%s", heure(débutPromise2), result));
}).finally(() => {
  // executed in all cases
  console.log(sprintf("[promise2.finally], %s", heure(débutPromise2)));
});
 
// will be displayed before the msg of asynchronous functions and those of associated events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

تعليقات

  • الأسطر 18–28: إنشاء [Promise promise1]. تعرض وظيفتها غير المتزامنة النتيجة عبر حدث بعد ثانية واحدة. بمجرد بدء هذه العملية غير المتزامنة (بدء المؤقت)، لا ننتظر حتى تعرض النتيجة وننتقل فورًا إلى الكود في السطر 35؛
  • الأسطر 35–44: نشترك في الحدثين [resolved، rejected] اللذين يمكن أن تصدرهما الوظيفة غير المتزامنة الداخلية لـ [promise1]؛
  • الأسطر 46-71: نكرر نفس تسلسل الكود كما في السابق لـ Promise ثانية [promise2]؛
  • السطر 74: انتهى تنفيذ الجزء الرئيسي من البرنامج النصي، لكن البرنامج النصي ككل لم ينتهِ بعد، لأن إجراءين غير متزامنين قد تم بدءهما. نعود إلى حلقة الأحداث، حيث سيحدث في مرحلة ما أحد الأحداث [resolved، rejected] للوعود [promise1، promise2]. سيتم التعامل معه بعد ذلك؛
  • ثم نعود إلى حلقة الأحداث. وهناك، سيتم التعامل مع الحدث الثاني [resolved، rejected] للوعود [promise1، promise2] عند حدوثه؛

التنفيذ


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-04.js"
[début du script], heure=09:39:05:950, durée= 0 seconde(s) et 3 millisecondes
[début fonction asynchrone de promise1], heure=09:39:05:958, durée= 0 seconde(s) et 0 millisecondes
[début fonction asynchrone de promise2], heure=09:39:05:959, durée= 0 seconde(s) et 1 millisecondes
[fin du code principal du script], heure=09:39:05:960, durée= 0 seconde(s) et 13 millisecondes
[fin fonction asynchrone de promise1], heure=09:39:06:977, durée= 1 seconde(s) et 19 millisecondes
[promise1.then], heure=09:39:06:980, durée= 1 seconde(s) et 21 millisecondes, result=[réussite]
[promise1.finally] heure=09:39:06:982, durée= 1 seconde(s) et 24 millisecondes
[fin fonction asynchrone de promise2], heure=09:39:07:976, durée= 2 seconde(s) et 17 millisecondes
[promise2.catch], heure=09:39:07:978, durée= 2 seconde(s) et 19 millisecondes, result=[échec]
[promise2.finally], heure=09:39:07:980, durée= 2 seconde(s) et 21 millisecondes
 
[Done] exited with code=0 in 2.589 seconds

تعليقات

  • السطر 3: يتم تشغيل الدالة غير المتزامنة لـ [promise1]، لكننا لا ننتظر اكتمالها، والذي سيتم الإشارة إليه بواسطة حدث؛
  • السطر 4: يتم تشغيل الدالة غير المتزامنة لـ [promise2]، لكننا لا ننتظر انتهاءها، والذي سيتم الإشارة إليه بواسطة حدث؛
  • السطر 5: نهاية الكود الرئيسي والعودة إلى حلقة الأحداث؛
  • السطر 6: معالجة حدث [نهاية الدالة غير المتزامنة لـ promise1]. ستتغير حالة [promise1] إلى [resolved]. ويشير حدث إلى ذلك؛
  • السطر 7: نظرًا لأن [promise2] لم تنتهِ من عملها بعد، فسيتم التعامل مع حدث [promise1 resolved] الذي تمت إضافته للتو إلى الحلقة بواسطة طريقة [promise1.then] ثم بواسطة طريقة [promise.finally] (السطر 8)؛
  • الأسطر 9-11: تحدث الآلية نفسها عندما يتغير [promise2] من الحالة [pending] إلى [resolved]؛

11.5. نص برمجي [async-05]

لنعد إلى كود منشئ كائن [Promise]:

1
2
3
4
5
6
7
8
9
'use strict'; 

const promise=new Promise(function(resolve, reject){
// an asynchronous task is launched
// …
// if successful: call resolve(result) where [result] is the result of the asynchronous task;
// if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to events issued by the asynchronous task

السطر 2: يتم تشغيل المهمة غير المتزامنة لـ [Promise]. غالبًا ما تتطلب هذه المهمة معلمات أكثر من مجرد معلمات [resolve، reject] التي تمررها إليها الدالة التي تغلفها. في هذه الحالة، نغلف إنشاء [Promise] في دالة ستمرر إليها المعلمات التي تحتاجها وظيفتها غير المتزامنة:

'use strict';

// definition of the asynchronous function
function uneFonctionAsynchrone (p1, p2, , pn){
 return new Promise(function(resolve, reject){
     // an asynchronous task is launched with parameters (P1, p2, ..., pn)
    // …
     // if successful: call resolve(result) where [result] is the result of the asynchronous task;
     // if unsuccessful: call reject(error) where [error] is an object encapsulating the error encountered;
}
// subscribe to [resolved, rejected] events to be sent by the asynchronous function [uneFonctionAsynchrone]

// some time later, the asynchronous function [uneFonctionAsynchrone] is called
uneFonctionAsynchrone(e1, e2, , en) ;

النص البرمجي التالي:

  • يحدد دالتين غير متزامنتين تعيدان [Promise]؛
  • يبدأ تنفيذهما بالتوازي وينتظر انتهاء كليهما قبل تنفيذ مهمة معينة؛

'use strict';
 
// we can define asynchronous functions that render a [Promise] type
// they can then be tagged with the keyword [async]
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du script],", heure());
 
// an asynchronous function that renders a promise [Promise]
function async01(p1) {
  return new Promise((resolve) => {
    console.log("[début de la tâche asynchrone async01]");
    // the asynchronous task
    const débutAsync01 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async01],", heure(débutAsync01));
      // the asynchronous task can render a complex result
      resolve({
        prop1: [10, 20, 30],
        prop2: "abcd",
        prop3: p1,
      });
    }, 1000)
  });
}
 
// an asynchronous function that renders a promise [Promise]
function async02(p1, p2) {
  return new Promise(resolve => {
    console.log("[début de la tâche asynchrone async02]");
    // asynchronous task
    const débutAsync02 = moment(Date.now());
    setTimeout(function () {
      console.log("[fin de la tâche asynchrone async02],", heure(débutAsync02));
      // the asynchronous task can render a complex result
      resolve({
        prop1: [11, 21, 31],
        prop2: "xyzt",
        prop3: p1 + p2
      });
    }, 2000)
  })
}
 
// run the two asynchronous functions in parallel
// and wait for them both to finish
// the then executes only if both functions have issued the [resolved] event
// the catch is executed as soon as one of the two functions issues the [rejected] event
Promise.all([async01(10), async02(10, 20)])
  // the result is an array [result1, result2] where [result1] is the result emitted by a [resolve] of [async01]
  // and [result2] the result emitted by a [resolve] of [async02]
  .then(result => {
    console.log(sprintf("[promise-all success], %s, result=%j", heure(débutScript), result));
  })
  // error is the result emitted by the first [reject] of one of the two asynchronous functions
  .catch(error => {
    console.log(sprintf("[promise-all error], %s, erreur=%j", heure(débutScript), error));
  })
  // finally is executed after then or catch
  .finally(() => {
    console.log(sprintf("[promise-all finally], %s", heure(débutScript)));
  });
 
// will be displayed before msgs for asynchronous functions and associated events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

تعليقات

  • الأسطر 15–30: نُعرّف دالة [async01] التي تُرجع نتيجتها بعد ثانية واحدة عبر حدث مؤقت. تُستخدم الدالة [async01] في النتيجة في السطر 26؛
  • الأسطر 33–47: نفعل الشيء نفسه مع الدالة [async02] التي تُرجع نتيجتها بعد ثانيتين عبر حدث مؤقت. تأخذ الدالة [async02] معلمتين، تُستخدمان في نتيجتها في السطر 44؛
  • عند استدعاء الدالتين [async01] و [async02]:
    • سيتم تشغيل؛
    • ستُرجع وعدين [promise1، promise2] إلى الكود المستدعي؛
    • سيعود التنفيذ بعد ذلك إلى الكود المستدعي، الذي سيستمر في العمل؛
    • بعد حوالي ثانية واحدة، ستصدر [async01] حدثًا للإشارة إلى أنها أكملت عملها. سيتم وضع الحدث المعني في قائمة انتظار حلقة الأحداث المرتبطة بالنتيجة التي تم تمريرها بواسطة [async01] جنبًا إلى جنب مع الحدث؛
    • بعد حوالي ثانيتين، ستحدث نفس العملية لـ [async02]؛
  • السطر 54: الآن فقط يتم تنفيذ الدالتين غير المتزامنتين [async01، async02] (الرموز async01(10) و async02(10,20)). يتم تنفيذهما داخل مصفوفة تم تمريرها كمعلمة إلى طريقة [Promise.all]. نعلم أن [async01، async02] تعيدان كلاهما وعدًا إلى الكود المستدعي. لذلك، فإن معلمة [Promise.all] هي مصفوفة من وعدين؛
  • [Promise.all([promise1, promise2, …, promisen]).then(f1).catch(f2).finally(f3)] هو اشتراك في حدث:
    • [Promise.all] من النوع [Promise]؛
    • سيتم تنفيذ الدالة [f1] الخاصة بالطريقة [then] عندما تنتقل جميع الوعود [promise1, promise2, …, promisen] الموجودة في مصفوفة معلمة الطريقة [all] من الحالة [pending] إلى الحالة [resolved]. بعبارة أخرى، سيتم تنفيذ [f1] عندما تكتمل جميع الوعود الموجودة في المصفوفة بنجاح؛
    • سيتم تنفيذ الدالة [f2] الخاصة بالطريقة [catch] بمجرد أن تتغير أي من الوعود في المصفوفة من الحالة [pending] إلى الحالة [rejected]. بعبارة أخرى، يتم تنفيذ [f2] بمجرد فشل أي من الوعود في المصفوفة؛
    • سيتم تنفيذ الدالة [f3] الخاصة بالطريقة [finally] بعد تنفيذ إحدى طريقتي [then، catch]، لذا يتم تنفيذها دائمًا؛

يؤدي تنفيذ الكود إلى النتائج التالية:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-05.js"
[début du script], heure=12:17:17:367
[début de la tâche asynchrone async01]
[début de la tâche asynchrone async02]
[fin du code principal du script], heure=12:17:17:375, durée= 0 seconde(s) et 10 millisecondes
[fin de la tâche asynchrone async01], heure=12:17:18:391, durée= 1 seconde(s) et 17 millisecondes
[fin de la tâche asynchrone async02], heure=12:17:19:389, durée= 2 seconde(s) et 14 millisecondes
[promise-all success], heure=12:17:19:390, durée= 2 seconde(s) et 25 millisecondes, result=[{"prop1":[10,20,30],"prop2":"abcd","prop3":10},{"prop1":[11,21,31],"prop2":"xyzt","prop3":30}]
[promise-all finally], heure=12:17:19:392, durée= 2 seconde(s) et 27 millisecondes
 
[Done] exited with code=0 in 2.572 seconds
  • السطران 6-7: تم تشغيل المهمتين غير المتزامنتين [async01، async02]. تعملان بالتوازي. ليس تنفيذ كودهما هو ما يحدث بالتوازي، بل إن انتظارهما للبيانات المطلوبة يحدث في نفس الوقت؛
  • السطر 5: انتهى الجزء الرئيسي من البرنامج النصي. الآن علينا فقط انتظار اكتمال المهمتين غير المتزامنتين [async01، async02]؛
  • السطر 6: تكتمل المهمة غير المتزامنة [async01] بعد حوالي ثانية واحدة من إطلاقها. وهي تُرجع نتيجة باستخدام الدالة [resolve]، لذا يتغير وعدها في المصفوفة في السطر 56 من الكود من الحالة [pending] إلى [resolved]. وهذا لا يكفي لتشغيل الطريقة [then]، السطران 59-60 من الكود؛
  • السطر 7: تكتمل المهمة غير المتزامنة [async02] بعد حوالي ثانيتين من إطلاقها. وهي تُرجع نتيجة باستخدام دالة [resolve]، لذا يتغير وعدها في المصفوفة في السطر 56 من الكود من الحالة [pending] إلى [resolved]. سيتم تنفيذ طريقة [then] بمجرد أن تسمح حلقة الأحداث بذلك؛
  • السطر 8: يتم تنفيذ طريقة [then] الخاصة بـ [Promise.all]. تتلقى كمعلمة مصفوفة [result1, result2] حيث [result1] هي النتيجة التي تعيدها [async01]، و[result2] هي النتيجة التي تعيدها [async02]؛
  • السطر 9: يتم تنفيذ طريقة [finally] الخاصة بـ [Promise.all]؛

11.6. نص برمجي [async-06]

يوضح هذا البرنامج النصي الجديد كيف أن الاستخدام المشترك لكلمتي [async / await] يتيح كتابة كود غير متزامن يشبه الكود المتزامن. يتم إخفاء معالجة الأحداث تمامًا، مما يجعل الكود أسهل في الفهم.

نعيد النظر في المثال السابق مع التعديلات التالية:

  • نضيف دالة غير متزامنة ثالثة [async03والتي تُرجع نتيجتها باستخدام طريقة [Promise.reject]، مما يشير إلى حلقة الأحداث بأنها "فشلت" في إكمال مهمتها؛
  • نقوم بتنفيذ الوظائف غير المتزامنة الثلاث [async01، async02، async03] بالتسلسل. في المثال السابق، كنا قد نفذنا الوظائف غير المتزامنة [async01، async02] بالتوازي؛
  • قبل إدخال الكلمات الرئيسية [async/await]، كان التنفيذ التسلسلي للأعمال غير المتزامنة يتم باستخدام كائنات [Promise] متداخلة. كلما كان هناك عدة أعمال غير متزامنة لتنفيذها بهذه الطريقة، زاد عدد الوعود وفقًا لذلك، وأصبح الكود أقل قابلية للقراءة؛
  • مع الكلمات الرئيسية [async/await]، يستخدم التنفيذ التسلسلي للمهام غير المتزامنة صيغة مشابهة لتلك المستخدمة في تنفيذ المهام المتزامنة:

// asynchronous function - using async / await
async function main() {
  // sequential execution of asynchronous tasks
  try {
    // execution while waiting for [async01]
    const result1 = await async01(...);
    console.log("[async01 result]=", result1);
    // execution while waiting for [async02]
    const result2 = await async02(...);
    console.log("[async02 result]=", result2);
    // execution while waiting for [async03]
    const result3 = await async03(...);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[sequential error]= %j, %s", error));
  } finally {
    // completed
    console.log("[fin exécution séquentielle des tâches asynchrones],");
  }
  • السطر 6: يتم تشغيل الدالة غير المتزامنة [async01] (باستخدام الكلمة الرئيسية await) وننتظر حتى تعيد النتيجة عبر إحدى الطرق [Promise.resolve، Promise.reject]. وبالتالي، فهذه عملية حجب؛
  • السطر 6: تعمل الكلمة الرئيسية [await] على تحويل العملية غير المتزامنة [async01] إلى عملية حجب. ونعلم أن العملية [async01] تُرجع نتيجة بطريقتين:
    • تُرجع كائن [Promise] إلى الكود المستدعي على الفور تقريبًا؛
    • ثم تنشر نتيجة إلى حلقة الأحداث عبر طرق [Promise.resolve، Promise.reject]. وهذه النتيجة الأخيرة هي التي يستردها [result1] في السطر 6. أصبحت المعالجة المدفوعة بالأحداث لعملية [async01] غير مرئية؛
    • إذا تم نشر نتيجة [result] لـ [async01] عبر [Promise.resolve(result)]، يتم تعيينها إلى [result1] في السطر 6 ويستمر التنفيذ إلى السطر 7؛
    • إذا تم تحليل نتيجة [async01] عبر [Promise.reject]، فإن ذلك يؤدي إلى حدوث استثناء ويستمر تنفيذ الكود إلى السطر 14، أي كتلة catch. المعلمة الخاصة بجملة [catch] هي كائن الخطأ (error) الذي تم تحليله بواسطة [async01] باستخدام التعبير [Promise.reject(error)]. يمكن للمهمة غير المتزامنة أيضًا إصدار الخطأ عبر [throw(error)]. كائن [error] هو الكائن الذي تم التقاطه في [catch(error)]؛
    • يجب أن تكون الكلمة الرئيسية [await] داخل دالة مسبوقة بالكلمة الرئيسية [async]، السطر 2. تشير هذه الكلمة الرئيسية إلى أن الدالة [main] هي دالة غير متزامنة؛
    • في التعبير [await f(…)]، يجب أن تكون [f] دالة غير متزامنة تُرجع كائن [Promise] إلى الكود المستدعي؛
  • نقوم بنفس الشيء بالنسبة للإجراءات غير المتزامنة [async02] في السطر 9 و[async03] في السطر 12؛

مع الاستمرار في استخدام الكلمات الرئيسية [async / await]، من الممكن تنفيذ المهام غير المتزامنة بالتوازي باستخدام الصيغة التالية:


try {
    // parallel execution of asynchronous tasks
    const result = await Promise.all([async01(...), async02(...), async03(...)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completed
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
}
  • السطر 3: لدينا عملية حجب: ننتظر حتى تنشر المهام الثلاث غير المتزامنة في المصفوفة [async01(..), async02(..), async03(..)] نتائجها في حلقة الأحداث باستخدام إحدى الطرق [Promise.resolve, Promise.reject]؛
  • إذا نشرت المهام غير المتزامنة الثلاث نتائجها باستخدام [Promise.resolve]، فإن الثابت [result] يكون عندئذ المصفوفة [result1, result2, result3] حيث:
    • [result1] هي النتيجة التي نشرتها [async01] باستخدام التعبير [Promise.resolve(result1)]؛
    • [result2] هي النتيجة التي نشرتها [async02] باستخدام التعبير [Promise.resolve(result2)]؛
    • [result3] هي النتيجة التي نشرتها [async03] باستخدام التعبير [Promise.resolve(result3)]؛
  • إذا نشرت أي من المهام الثلاث نتيجتها باستخدام التعبير [Promise.reject(error)]، فسيحدث استثناء؛
    • لا تُعيَّن قيمة للثابت [result] في السطر 3؛
    • يستمر التنفيذ مباشرة إلى كتلة [catch] في السطر 5؛
    • المعلمة (error) في catch هي الكائن (error) الذي تم نشره بواسطة التعبير [Promise.reject(error)]؛

من خلال الجمع بين هاتين الصيغتين، يمكننا تنفيذ المهام غير المتزامنة إما بالتسلسل أو بالتوازي، وكل ذلك باستخدام صيغة مشابهة لتلك المستخدمة في الكود المتزامن. لذلك، ينبغي أن نفضل هذه الصيغة، التي هي أسهل في القراءة بكثير من الصيغ السابقة. لم تتوفر صيغة [async/await] هذه إلا منذ إصدار ECMAScript 6. لا يزال هناك الكثير من كود JavaScript الذي يستخدم الوعود [Promise]. ولهذا السبب، من المهم أيضًا فهم كيفية عملها.

فيما يلي الكود الكامل لبرنامج [async-06]:


'use strict';
 
// parallel or sequential execution of several asynchronous tasks
// with the keywords async / await
 
// imports
import moment from 'moment';
import { sprintf } from 'sprintf-js';
 
// start
const débutScript = moment(Date.now());
console.log("[début du code principal du script],", heure());

// an asynchronous function rendering a [Promise]
function async01(débutAsync01) {
  return new Promise(function (resolve) {
    console.log("[début fonction asynchrone async01],", heure());
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async01],", heure(débutAsync01));
      // asynchronous action can make a result complex
      // here success
      resolve({
        prop1: [11, 21, 31],
        prop2: "abcd"
      });
    }, 1000)
  });
}
 
// an asynchronous function rendering a [Promise]
function async02(débutAsync02) {
  console.log("[début fonction asynchrone async02],", heure());
  return new Promise(function (resolve) {
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async02],", heure(débutAsync02));
      // asynchronous action can make a result complex
      // here success
      resolve({
        prop1: [12, 22, 32],
        prop2: "xyzt"
      });
    }, 2000)
  })
}
 
// an asynchronous function rendering a [Promise]
function async03(débutAsync03) {
  console.log("[début fonction asynchrone async03],", heure());
  return new Promise((resolve, reject) => {
    // asynchronous function
    setTimeout(function () {
      console.log("[fin fonction asynchrone async03],", heure(débutAsync03));
      // asynchronous action can make a result complex
      // here failure
      reject({
        prop1: [13, 23, 33],
        prop2: "échec"
      });
    }, 3000)
  })
}
 
// asynchronous function - using async / await
async function main() {
  const débutSequential = moment(Date.now());
  // sequential execution of asynchronous tasks
  console.log("------------ exécution séquentielle des tâches asynchrones lancée ------------------------")
  try {
    // execution while waiting for [async01]
    const débutAsync01 = moment(Date.now());
    const result1 = await async01(débutAsync01);
    console.log("[async01 result]=", result1);
    // execution while waiting for [async02]
    const débutAsync02 = moment(Date.now());
    console.log("début async02-------------", heure());
    const result2 = await async02(débutAsync02);
    console.log("[async02 result]=", result2);
    // execution while waiting for [async03]
    const débutAsync03 = moment(Date.now());
    console.log("début async03-------------", heure());
    const result3 = await async03(débutAsync03);
    console.log("[async03 result]=", result3);
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[sequential error]= %j, %s", error, heure(débutSequential)));
  } finally {
    // completed
    console.log("[fin exécution séquentielle des tâches asynchrones],", heure(débutSequential));
  }
 
  const débutParallel = moment(Date.now());
  // parallel execution of asynchronous tasks
  console.log("------------ exécution parallèle des tâches asynchrones lancée ------------------------")
  try {
    const result = await Promise.all([async01(débutParallel), async02(débutParallel), async03(débutParallel)]);
    console.log(sprintf("[parallel success], %s, result=%j", heure(débutParallel), result));
  } catch (error) {
    // one of the asynchronous actions has failed
    console.log(sprintf("[parallel error], %s, erreur=%j", heure(débutParallel), error));
  } finally {
    // completed
    console.log(sprintf("[fin exécution parallèle des tâches asynchrones],%s", heure(débutParallel)));
  }
 
  // completed
  console.log("[fin de la fonction main],", heure(débutSequential));
}
// execution asynchronous function main
main();
 
// will be displayed before the various msgs for asynchronous functions and their events
console.log("[fin du code principal du script],", heure(débutScript));
 
// utility
function heure(début) {
  // current time
  const now = moment(Date.now());
  // time formatting
  let result = "heure=" + now.format("HH:mm:ss:SSS");
  if (début) {
    const durée = now - début;
    const milliseconds = durée % 1000;
    const seconds = Math.floor(durée / 1000);
    // formatting duration
    result = result + sprintf(", durée= %s seconde(s) et %s millisecondes", seconds, milliseconds);
  }
  // result
  return result;
}

نتائج التنفيذ هي كما يلي:


[Running] C:\myprograms\laragon-lite\bin\nodejs\node-v10\node.exe -r esm "c:\Data\st-2019\dev\es6\javascript\async\async-06.js"
[début du code principal du script], heure=15:02:00:152
------------ exécution séquentielle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:00:161
[fin du code principal du script], heure=15:02:00:164, durée= 0 seconde(s) et 15 millisecondes
[fin fonction asynchrone async01], heure=15:02:01:165, durée= 1 seconde(s) et 4 millisecondes
[async01 result]= { prop1: [ 11, 21, 31 ], prop2: 'abcd' }
début async02------------- heure=15:02:01:253
[début fonction asynchrone async02], heure=15:02:01:254
[fin fonction asynchrone async02], heure=15:02:03:265, durée= 2 seconde(s) et 12 millisecondes
[async02 result]= { prop1: [ 12, 22, 32 ], prop2: 'xyzt' }
début async03------------- heure=15:02:03:268
[début fonction asynchrone async03], heure=15:02:03:268
[fin fonction asynchrone async03], heure=15:02:06:285, durée= 3 seconde(s) et 18 millisecondes
[sequential error]= {"prop1":[13,23,33],"prop2":"échec"}, heure=15:02:06:289, durée= 6 seconde(s) et 129 millisecondes
[fin exécution séquentielle des tâches asynchrones], heure=15:02:06:291, durée= 6 seconde(s) et 131 millisecondes
------------ exécution parallèle des tâches asynchrones lancée ------------------------
[début fonction asynchrone async01], heure=15:02:06:292
[début fonction asynchrone async02], heure=15:02:06:293
[début fonction asynchrone async03], heure=15:02:06:294
[fin fonction asynchrone async01], heure=15:02:07:294, durée= 1 seconde(s) et 2 millisecondes
[fin fonction asynchrone async02], heure=15:02:08:298, durée= 2 seconde(s) et 6 millisecondes
[fin fonction asynchrone async03], heure=15:02:09:297, durée= 3 seconde(s) et 5 millisecondes
[parallel error], heure=15:02:09:298, durée= 3 seconde(s) et 6 millisecondes, erreur={"prop1":[13,23,33],"prop2":"échec"}
[fin exécution parallèle des tâches asynchrones],heure=15:02:09:299, durée= 3 seconde(s) et 7 millisecondes
[fin de la fonction main], heure=15:02:09:300, durée= 9 seconde(s) et 140 millisecondes
 
[Done] exited with code=0 in 9.668 seconds