تصور الوعود وعدم التزامن / انتظار



يوم جيد يا اصدقاء!

أقدم لكم ترجمة مقالة "JavaScript Visualized: Promises & Async / Await" للمؤلفة ليديا هالي.

هل صادفت شفرة جافا سكريبت التي لا تعمل كما هو متوقع؟ عندما يتم تنفيذ الوظائف في أمر تعسفي ، لا يمكن التنبؤ بها ، أو يتم تأجيلها. واحدة من المهام الرئيسية للوعود هي تبسيط تنفيذ الوظائف.

لقد دفع فضولي الذي لا يشبع وليالي بلا نوم بالكامل - بفضلهم أنشأت العديد من الرسوم المتحركة. حان الوقت للحديث عن الوعود: كيف تعمل ، ولماذا يجب استخدامها ، وكيف يتم ذلك.

المقدمة


عند كتابة كود JS ، غالبًا ما يتعين علينا التعامل مع المهام التي تعتمد على مهام أخرى. لنفترض أننا نريد الحصول على صورة وضغطها وتطبيق مرشح عليها وحفظها.

بادئ ذي بدء ، نحن بحاجة إلى الحصول على صورة. للقيام بذلك ، استخدم الوظيفة getImage. بعد تحميل الصورة ، نمررها إلى الوظيفة resizeImage. بعد ضغط الصورة ، نقوم بتطبيق مرشح عليها باستخدام الوظيفة applyFilter. بعد الضغط على الفلتر وتطبيقه ، نقوم بحفظ الصورة وإبلاغ المستخدم بالنجاح.

ونتيجة لذلك ، نحصل على ما يلي:



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

لحسن الحظ ، لدينا اليوم وعود.



وعد بناء الجملة


تم تقديم الوعود في ES6. في العديد من الكتيبات يمكنك قراءة ما يلي:

الوعد (الوعد) هو قيمة يتم الوفاء بها أو رفضها في المستقبل.

نعم ... شرح ذلك. في وقت ما ، جعلني أفكر في الوعود بأن تكون شيئًا غريبًا وغامضًا ونوعًا من السحر. ما هم حقا؟

يمكننا إنشاء وعد مع مُنشئ Promiseيأخذ وظيفة رد الاتصال كحجة. رائع ، دعنا نحاول:



انتظر ، ما الذي سيعود هنا؟

Promiseهو كائن يحتوي على الحالة ( [[PromiseStatus]]) والقيمة ( [[PromiseValue]]). في المثال أعلاه، فإن القيمة [[PromiseStatus]]هي pending، وقيمة الوعد undefined.

لا تقلق، لم يكن لديك للتفاعل مع هذا الكائن، لا يمكنك حتى الوصول إلى [[PromiseStatus]]و الخصائص [[PromiseValue]]. ومع ذلك ، فإن هذه الخصائص مهمة للغاية عند العمل مع الوعود.

PromiseStatus أو حالة الوعد يمكن أن تأخذ واحدة من ثلاث قيم:

  • fulfilled: resolved (). ,
  • rejected: rejected (). -
  • pending: , pending (, )

يبدو رائعًا ، ولكن متى يحصل الوعد على الحالات المشار إليها؟ ولماذا يهم الوضع؟

في المثال أعلاه ، نقوم بتمرير Promiseوظيفة رد اتصال بسيطة للمنشئ () => {}. هذه الوظيفة في الواقع تأخذ حجتين. قيمة الوسيطة الأولى ، التي تسمى عادةً resolveأو res، هي الطريقة التي يتم استدعاؤها عند تنفيذ الوعد. قيمة الوسيطة الثانية ، تسمى عادة rejectأو rej، هي الطريقة التي تسمى عندما يتم رفض الوعد عندما حدث خطأ ما.



دعونا نرى ما هو الإخراج إلى وحدة التحكم عند استدعاء الأساليب resolveو reject:



رائع! الآن نعرف كيف نتخلص من المكانة pendingوالمعنى undefined. حالة الوعد عند استدعاء الأسلوب resolveهو fulfilled، عندما reject-rejected.

[[PromiseValue]]أو قيمة الوعد هي القيمة التي نمررها إلى الأساليب resolveأو rejectكحجة.

حقيقة ممتعة: أشار Jake Archibald بعد قراءة هذا المقال إلى خطأ في Chrome ، والذي fulfilledعاد بدلاً من ذلك resolved.



حسنًا ، نعرف الآن كيفية العمل مع الكائن Promise. ولكن ما هو استخدامها؟

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

لحسن الحظ ، الوعود تساعد على التعامل مع هذا. نعيد كتابة الرمز بحيث تُعيد كل دالة وعدًا.

إذا تم تحميل الصورة ، فإننا ننفذ الوعد. خلاف ذلك ، في حالة حدوث خطأ ، رفض الوعد:



دعنا نرى ما يحدث عند تشغيل هذا الرمز في المحطة:



Cool! إرجاع Promis مع بيانات تحليل ("تحليل") ، كما توقعنا.

لكن ... ما هي الخطوة التالية؟ نحن لسنا مهتمين بموضوع promis ، نحن مهتمون ببياناته. هناك 3 طرق مضمنة للحصول على قيمة وعد:

  • .then(): يسمى بعد تنفيذ الوعد
  • .catch(): دعت بعد رفض الوعد
  • .finally(): يتم الاتصال به دائمًا ، بعد التنفيذ وبعد رفض الوعد



.thenتأخذ الطريقة القيمة التي تم تمريرها إلى الطريقة resolve:



الطريقة .catchتأخذ القيمة التي تم تمريرها إلى الطريقة reject:



أخيرًا ، حصلنا على القيمة المطلوبة. يمكننا أن نفعل أي شيء بهذه القيمة.

عندما نكون واثقين من تنفيذ أو رفض الوعد ، يمكنك الكتابة Promise.resolveإما Promise.rejectبالقيمة المناسبة.



هذه هي الصيغة التي سيتم استخدامها في الأمثلة التالية.



والنتيجة .thenهي قيمة الوعد (أي أن هذه الطريقة تعيد الوعد أيضًا). هذا يعني أنه يمكننا استخدام قدر .thenالحاجة: .thenيتم تمرير نتيجة السابقة كحجة إلى التالية .then.



في getImageيمكننا استخدام عدة .thenلنقل الصورة المصنعة للوظيفة القادمة.



تبدو هذه الصيغة أفضل بكثير من سلم وظائف رد الاتصال المتداخلة.



المهام الصغيرة والمهام (الماكرو)


حسنًا ، نحن الآن نعرف كيفية إنشاء الوعود وكيفية استخراج القيم منها. أضف بعض التعليمات البرمجية إلى البرنامج النصي الخاص بنا وقم بتشغيله مرة أخرى:



أولاً ، يتم عرضه في وحدة التحكم Start!. هذا أمر طبيعي ، لأن لدينا السطر الأول من التعليمات البرمجية console.log('Start!'). القيمة الثانية المعروضة على وحدة التحكم End!، ليست قيمة الوعد المكتمل. يتم عرض قيمة الوعد الأخير. لماذا حصل هذا؟

هنا نرى قوة الوعود. على الرغم من أن JS هو ترابط واحد ، يمكننا جعل الشفرة غير متزامنة مع Promise.

أين يمكن أن نلاحظ السلوك غير المتزامن؟ بعض الطرق المضمنة في المتصفح ، مثل setTimeout، يمكن أن تحاكي التزامن.

حق في حلقة الأحداث (حلقة الأحداث) ، هناك نوعان من قوائم الانتظار: قائمة انتظار المهام (الماكرو) أو ببساطة المهام (الماكرو) قائمة انتظار المهام ، وقائمة انتظار المهام) وقائمة المهام الدقيقة أو المهام الصغيرة فقط (قائمة المهام الصغيرة ، والمهام الدقيقة).

ماذا ينطبق على كل منهم؟ باختصار ، إذن:

  • مهام الماكرو: setTimeout ، setInterval ، setImmediate
  • Microtasks: process.nextTick ، ​​Promise callback ، queueMicrotask

نرى Promiseفي قائمة المهام الدقيقة. عند Promiseتنفيذ الطريقة واستدعاؤها then()، catch()أو finally()، تتم إضافة وظيفة رد الاتصال مع الطريقة إلى قائمة انتظار المهام الدقيقة. وهذا يعني أن الاستدعاء باستخدام الطريقة لا يتم تنفيذه على الفور ، مما يجعل كود JS غير متزامن.

عندما هو الأسلوب then()، catch()أو finally()غير ذلك أعدم؟ المهام في حلقة الحدث لها الأولوية التالية:

  1. أولاً ، يتم تنفيذ الوظائف الموجودة في مكدس الاستدعاءات. تتم إزالة القيم التي تم إرجاعها بواسطة هذه الدالات من المكدس.
  2. بعد تحرير المكدس ، يتم وضع المهام الدقيقة وتنفيذها واحدة تلو الأخرى (يمكن للمهام الدقيقة إرجاع مهام دقيقة أخرى ، مما يؤدي إلى إنشاء دورة لا نهاية لها من المهام الدقيقة).
  3. بعد تحرير المكدس وقائمة انتظار المهام الدقيقة ، تقوم حلقة الحدث بالتحقق من وحدات الماكرو. يتم دفع مهام الماكرو إلى المكدس وتنفيذها وحذفها.



فكر في مثال:

  • Task1: دالة يتم إضافتها إلى المكدس فورًا ، على سبيل المثال ، عن طريق استدعاء في التعليمات البرمجية.
  • Task2، Task3، Task4: مثال Mikrozadachi thenProMIS أو المهمة المضافة عبر queueMicrotask.
  • Task5، Task6: مهام الماكرو ، على سبيل المثال ، setTimeoutأوsetImmediate



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

كلمات كافية. لنكتب الرمز.



في هذا الكود ، لدينا setTimeoutمهمة ماكرو ومهمة صغيرة .then. قم بتشغيل التعليمات البرمجية ومعرفة ما يتم عرضه في وحدة التحكم.

ملاحظة: في المثال أعلاه، وأنا استخدم أساليب مثل console.log، setTimeoutو Promise.resolve. كل هذه الطرق داخلية ، لذلك لا تظهر في تتبع المكدس - لا تفاجأ عندما لا تجدها في أدوات استكشاف الأخطاء وإصلاحها في المستعرض.

في السطر الأول لديناconsole.log. تتم إضافته إلى المكدس وعرضه في وحدة التحكم Start!. بعد ذلك ، تتم إزالة هذه الطريقة من المكدس ويستمر المحرك في تحليل الرمز.



يصل المحرك setTimeout، الذي يضاف إلى المكدس. هذه الطريقة هي طريقة متصفح مدمجة: () => console.log('In timeout')تتم إضافة وظيفة رد الاتصال ( ) الخاصة بها إلى Web API وهي موجودة قبل انطلاق المؤقت. على الرغم من حقيقة أن عداد المؤقت هو 0 ، لا يزال يتم وضع رد الاتصال أولاً في WebAPI ثم في قائمة انتظار المهام الكلية: setTimeout- هذه مهمة ماكرو.



بعد ذلك ، يصل المحرك إلى الطريقة Promise.resolve(). تتم إضافة هذه الطريقة إلى المكدس ، ثم يتم تنفيذها بقيمة Promise. thenيتم وضع رد الاتصال الخاص به في قائمة انتظار المهام الدقيقة.



وأخيرًا ، يصل المحرك إلى الطريقة الثانية console.log(). يتم دفعها على الفور إلى المكدس ، ويتم إخراجها إلى وحدة التحكمEnd!، تتم إزالة الطريقة من المكدس ، ويستمر المحرك.



المحرك "يرى" أن المكدس فارغ. التحقق من قائمة انتظار المهام. تقع هناك then. يتم دفعها إلى المكدس ، يتم عرض قيمة الوعد على وحدة التحكم: في هذه الحالة ، سلسلة Promise!.



يرى المحرك أن المكدس فارغ. انه "يبدو" في طابور المهام الدقيقة. إنها فارغة أيضًا.

حان الوقت للتحقق من قائمة انتظار المهام الكلية: إنها موجودة setTimeout. يتم دفعها إلى المكدس وإرجاع طريقة console.log(). يتم إخراج سلسلة إلى وحدة التحكم 'In timeout!'. setTimeoutتتم إزالته من المكدس.



منجز. الآن سقط كل شيء في مكانه ، أليس كذلك؟



غير متزامن / انتظار


قدم ES7 طريقة جديدة للعمل مع التعليمات البرمجية غير المتزامنة في JS. استخدام الكلمات الرئيسية asyncو awaitنحن يمكن أن تخلق وظيفة غير متزامن وترجع ضمنا وعد. ولكن كيف نفعل ذلك؟

وفي وقت سابق، ناقشنا كيفية إنشاء صراحة كائن Promise: استخدام new Promise(() => {})، Promise.resolveأو Promise.reject.

بدلاً من ذلك ، يمكننا إنشاء دالة غير متزامنة تقوم بإرجاع الكائن المحدد ضمنياً. هذا يعني أننا لم نعد بحاجة إلى الإنشاء يدويًا Promise.



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

اتضح أنه يمكننا تأخير تنفيذ وظيفة غير متزامنة؟ عظيم ، لكن ... ماذا يعني ذلك؟

دعنا نرى ما يحدث عند تشغيل التعليمات البرمجية التالية:







يرى المحرك في البداية console.log. يتم دفع هذه الطريقة على المكدس وعرضها على وحدة التحكم Before function!.



ثم يتم استدعاء الوظيفة غير المتزامنة myFunc()، ويتم تنفيذ التعليمات البرمجية الخاصة بها. في السطر الأول من هذا الرمز ، نسمي الثاني console.logبخط 'In function!'. تتم إضافة هذه الطريقة إلى المكدس ، ويتم عرض قيمتها على وحدة التحكم ، ويتم إزالتها من المكدس.



يتم تنفيذ رمز الوظيفة بعد ذلك. في السطر الثاني لدينا كلمة رئيسية await.

أول شيء يحدث هنا هو تنفيذ القيمة المتوقعة: في هذه الحالة ، الوظيفة one. يتم دفعه إلى المكدس ويعيد الوعد. بعد اكتمال الوعد ، oneوإرجاع الوظيفة القيمة ، يرى المحرك await.

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



بعد تأخير تنفيذ الوظيفة غير المتزامنة ، يعود المحرك إلى تنفيذ التعليمات البرمجية في سياق عام.



بعد تنفيذ جميع التعليمات البرمجية في سياق عام ، تقوم حلقة الحدث بالتحقق من المهام الدقيقة والكشف myFunc(). myFunc()دفع على المكدس وتنفيذها. يحصل

المتغير resعلى قيمة الوعد الذي تم تنفيذه بواسطة الدالة one. نحن ندعو console.logبقيمة المتغير res: سلسلة One!في هذه الحالة. One!يتم عرضه في وحدة التحكم.

منجز. هل تلاحظ الفرق بين الوظيفة غير المتزامنة وطريقة thenpromis؟ الكلمة الرئيسيةawaitيؤجل تنفيذ وظيفة غير متزامنة. إذا استخدمنا then، فسيستمر جسم الوعد في الجري.



اتضح مطوّل. لا تقلق إذا شعرت بعدم الأمان عند التعامل مع الوعود. يستغرق الأمر بعض الوقت لتعتاد عليها. هذا أمر شائع في جميع تقنيات العمل مع التعليمات البرمجية غير المتزامنة في JS.

انظر أيضا "تصور عمل عمال الخدمة" .

شكرا لك على وقتك. آمل أن يكون قد تم إنفاقه بشكل جيد.

All Articles