مرحبا يا هبر! أقدم إليكم ترجمة المقال
"JavaScript Async / Await and Promises: Explated like you you five years" by Jack Pordi.
أي شخص يعتبر نفسه مطور جافا سكريبت ، في مرحلة ما ، يجب أن يكون واجه وظائف رد الاتصال ، أو الوعود ، أو في الآونة الأخيرة ، بناء جملة async / انتظار. إذا كنت في اللعبة لفترة كافية ، فمن المحتمل أنك واجهت أوقاتًا كانت وظائف رد الاتصال المتداخلة هي الطريقة الوحيدة لتحقيق عدم التزامن في JavaScript.
عندما بدأت التعلم والكتابة في JavaScript ، كان هناك بالفعل مليار برنامج تعليمي ودروس تعليمي تشرح كيفية تحقيق عدم التزامن في JavaScript. ومع ذلك ، فقد شرح الكثير منهم ببساطة كيفية تحويل وظائف رد الاتصال إلى وعود أو وعود في المزامنة / الانتظار. بالنسبة للكثيرين ، ربما يكون هذا أكثر من كافٍ للالتقاء بهم والبدء في استخدامه في الكود الخاص بهم.
ومع ذلك ، إذا كنت ، مثلي ، تريد حقًا فهم البرمجة غير المتزامنة (وليس فقط بناء جملة JavaScript!) ، فقد توافق معي على أن هناك نقصًا في المواد التي تشرح البرمجة غير المتزامنة من البداية.
ماذا يعني غير متزامن؟

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

تخيل أنك تطبخ حساء الخضار. للحصول على تشبيه جيد وبسيط ، افترض أن حساء الخضار يتكون فقط من البصل والجزر. قد تكون الوصفة لمثل هذا الحساء كما يلي:
- يقطع الجزر.
- يقطع البصل.
- أضف الماء إلى المقلاة ، ثم قم بتشغيل الموقد وانتظر حتى يغلي.
- يُضاف الجزر إلى المقلاة ويترك لمدة 5 دقائق.
- نضيف البصل إلى المقلاة ونطهو لمدة 10 دقائق أخرى.
هذه التعليمات بسيطة ومفهومة ، ولكن إذا كان أحدكم ، وهو يقرأ هذا ، يعرف حقًا كيف يطبخ ، فيمكنك القول إن هذه ليست الطريقة الأكثر فعالية للطهي. وسوف تكون على حق ، لهذا السبب:
- لا تتطلب منك الخطوات 3 و 4 و 5 فعلاً كطاهي فعل أي شيء باستثناء مراقبة العملية وتتبع الوقت.
- تتطلب منك الخطوات 1 و 2 القيام بشيء نشط.
لذلك ، قد تكون الوصفة لطهي أكثر خبرة على النحو التالي:
- البدء في غليان وعاء من الماء.
- أثناء انتظار الغليان ، ابدأ بقطع الجزر.
- عندما تنتهي من تقطيع الجزر ، يجب أن يغلي الماء ، لذا أضف الجزر.
- بينما ينضج الجزر في مقلاة ، يقطع البصل.
- يُضاف البصل ويُطهى لمدة 10 دقائق أخرى.
على الرغم من أن جميع الإجراءات ظلت كما هي ، إلا أنه يحق لك أن تتوقع أن يكون هذا الخيار أسرع وأكثر كفاءة. هذا هو بالضبط مبدأ البرمجة غير المتزامنة:
فأنت لا ترغب أبدًا في الجلوس ، في انتظار شيء ما ، بينما يمكنك قضاء وقتك في بعض الأشياء المفيدة الأخرى.
نعلم جميعًا أنه في البرمجة ، يحدث
انتظار شيء ما في كثير من الأحيان - سواء كان في انتظار استجابة HTTP من خادم أو إجراء من مستخدم أو أي شيء آخر. لكن دورات تنفيذ المعالج تعد ثمينة ويجب استخدامها
دائمًا بنشاط ، والقيام بشيء ما ، ولا تتوقع ذلك: ينتج عن هذا
البرمجة غير المتزامنة .
الآن دعنا نصل إلى JavaScript ، حسناً؟
لذلك ، باتباع نفس مثال حساء الخضار ، سأكتب بعض الوظائف لتمثيل خطوات الوصفة الموصوفة أعلاه.
أولاً ، لنكتب وظائف متزامنة تمثل مهام لا تستغرق وقتًا طويلاً. هذه هي وظائف JavaScript القديمة الجيدة ، لكن لاحظ أنني وصفت
chopOnions
و
chopOnions
كمهام تتطلب عملاً نشطًا (
chopOnions
) ، مما يسمح لهم بإجراء بعض العمليات الحسابية الطويلة. الكود الكامل متاح في نهاية المقال [1].
function chopCarrots() { console.log(" !"); } function chopOnions() { console.log(" !"); } function addOnions() { console.log(" !"); } function addCarrots() { console.log(" !"); }
قبل الانتقال إلى وظائف غير متزامنة ، سأشرح أولاً بسرعة كيف يتعامل نظام نوع JavaScript مع عدم التزامن:
يجب أن تكون جميع نتائج العمليات غير المتزامنة (بما في ذلك الأخطاء) ملفوفة في الوعد (الوعود) .
للحصول على وظيفة لإرجاع وعد ، يمكنك:
- أعد الوعد صراحةً ، أي
return new Promise(…)
؛ - إرجاع الوعد ضمنيًا - أضف
async
إلى إعلان الوظيفة ، أي async function foo()
؛ - استخدام كلا الخيارين .
هناك مقالة ممتازة [2] ، تتحدث عن الفرق بين الوظائف غير المتزامنة والوظائف التي ترجع الوعد. لذلك ، في مقالي ، لن أتطرق إلى هذا الموضوع ، الشيء الرئيسي الذي يجب تذكره: يجب عليك
دائمًا استخدام
async
غير المتزامنة في وظائف غير متزامنة.
لذلك ، وظائفنا غير المتزامنة ، التي تمثل الخطوات من 3-5 لتحضير حساء الخضار ، هي كما يلي:
async function letPotKeepBoiling(time) { return;
مرة أخرى ، قمت بحذف تفاصيل التنفيذ حتى لا يتم صرفها ، لكن تم نشرها في نهاية المقالة [1].
من المهم معرفة أنه من أجل انتظار نتيجة الوعد ، بحيث يمكنك فيما بعد القيام بشيء ما ، يمكنك ببساطة استخدام الكلمة الرئيسية التي
await
:
async function asyncFunction() { } result = await asyncFunction();
لذا ، نحن الآن بحاجة فقط إلى تجميعها جميعًا:
function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
لكن انتظر! هذا لا يعمل! سترى
SyntaxError: await is only valid in async functions
خطأ
SyntaxError: await is only valid in async functions
. لماذا؟ لأنك إذا لم تعلن عن وظيفة باستخدام
async
، فسوف تُعرّف JavaScript افتراضيًا بأنها وظيفة متزامنة - ولا يعني التزامن الانتظار! [3]. هذا يعني أيضًا أنه لا يمكنك استخدام
await
خارج الوظيفة.
لذلك ، نضيف ببساطة
async
إلى وظيفة
makeSoup
:
async function makeSoup() { const pot = boilPot(); chopCarrots(); chopOnions(); await pot; addCarrots(); await letPotKeepBoiling(5); addOnions(); await letPotKeepBoiling(10); console.log(" !"); } makeSoup();
وفويلا! لاحظ أنه في السطر الثاني ، أسمي الدالة غير المتزامنة
boilPot
بدون الكلمة الرئيسية التي
await
، لأننا لا نريد انتظار الغليان قبل بدء قطع الجزر. نتوقع الوعد
pot
في السطر الخامس قبل أن نحتاج إلى وضع الجزر في المقلاة ، لأننا لا نريد القيام بذلك قبل أن يغلي الماء.
ماذا يحدث أثناء
await
المكالمات؟ حسنا ، لا شيء ... نوع من ...
في سياق وظيفة
makeSoup
يمكنك ببساطة التفكير في الأمر كما تتوقع حدوث شيء ما (أو نتيجة ستعود في النهاية).
لكن تذكر:
أنت (مثل المعالج) لن ترغب أبدًا في الجلوس هناك وانتظر شيء ما ، بينما يمكنك قضاء وقتك في أشياء أخرى .
لذلك ، بدلاً من مجرد طهي الحساء ، يمكننا طهي شيء آخر بشكل متوازٍ:
makeSoup(); makePasta();
بينما نحن في انتظار
letPotKeepBoiling
، يمكننا ، على سبيل المثال ، طهي المعكرونة.
ترى؟ بناء جملة async / await سهل الاستخدام بالفعل ، إذا فهمت ذلك ، فهل توافق؟
ماذا عن الوعود العلنية؟
حسنًا ، إذا أصررت على ذلك ، فسوف أنتقل إلى استخدام الوعود الصريحة (ترجمة
تعليق.: من خلال الوعود الصريحة ، يشير المؤلف مباشرةً إلى بناء جملة الوعود ، وعن طريق الوعود الضمنية ، بناء الجملة المتزامن / الانتظار ، لأنه يُرجع الوعد ضمنيًا - لا حاجة إلى الكتابة return new Promise(…)
). ضع في اعتبارك أن أساليب المزامنة / الانتظار
تعتمد على الوعود بأنفسهم وبالتالي فإن كلا الخيارين متوافق تمامًا .
إن الوعود الصريحة ، في رأيي ، هي بين عمليات الاستدعاء على الطراز القديم والبناء الجنسي الجديد async / انتظار. بدلاً من ذلك ، يمكنك أيضًا التفكير في بناء الجملة غير المتزامن / في انتظار بناء الجملة ليس أكثر من الوعود الضمنية. في النهاية ، جاء إنشاء المزامنة / الانتظار بعد الوعود ، والذي جاء بدوره بعد وظائف رد الاتصال.
استخدم آلة وقتنا للانتقال إلى جحيم رد الاتصال [4]:
function callbackHell() { boilPot( () => { addCarrots(); letPotKeepBoiling(() => { addOnions(); letPotKeepBoiling(() => { console.log(" !"); }, 1000); }, 5000); }, 5000, chopCarrots(), chopOnions() ); }
لن أكذب ، كتبت هذا المثال على الطاير عندما كنت أعمل على هذا المقال ، واستغرق الأمر وقتًا أطول بكثير مما أرغب في الاعتراف به. قد لا يعرف الكثير منكم ما يجري هنا.
صديقي العزيز ، أليست كل وظائف رد الاتصال هذه سيئة؟ فليكن درسًا عدم استخدام وظائف رد الاتصال أبدًا ...وكما وعدنا ، نفس المثال مع وعود صريحة:
function makeSoup() { return Promise.all([ new Promise((reject, resolve) => { chopCarrots(); chopOnions(); resolve(); }), boilPot() ]) .then(() => { addCarrots(); return letPotKeepBoiling(5); }) .then(() => { addOnions(); return letPotKeepBoiling(10); }) .then(() => { console.log(" !"); }); }
كما ترى ، لا تزال الوعود تشبه وظائف رد الاتصال.
لن أخوض في التفاصيل ، لكن الأهم من ذلك:
.then
طريقة الوعد تأخذ نتيجتها وتمريرها إلى دالة الوسيطة (بشكل أساسي ، إلى وظيفة رد الاتصال ...)- لا يمكنك أبدًا استخدام نتيجة الوعد خارج سياق
.then
. في الأساس ، يشبه .then كتلة غير متزامنة تتوقع نتيجة ثم تقوم بتمريرها إلى وظيفة رد الاتصال. - بالإضافة إلى طريقة
.then
، هناك طريقة أخرى في .catch
- .catch
. هناك حاجة للتعامل مع الأخطاء في الوعود. لكنني لن أخوض في التفاصيل ، لأن هناك بالفعل مليار مقالة ودروس حول هذا الموضوع.
استنتاج
آمل أن تكون قد حصلت على فكرة عن الوعود والبرمجة غير المتزامنة من هذه المقالة ، أو على الأقل تعلمت مثالًا جيدًا من الحياة لتوضيح ذلك لشخص آخر.
لذا ، ما هي الطريقة التي تستخدمها: الوعود أم لا / تنتظر؟الجواب متروك لك تمامًا - وأود أن أقول إن الجمع بينهما ليس سيئًا للغاية ، لأن كلا الاتجاهين متوافق تمامًا مع بعضهما البعض.
ومع ذلك ، فأنا شخصياً 100٪ في مخيم async / await ، حيث أن الشفرة بالنسبة لي أكثر قابلية للفهم وتعكس بشكل أفضل تعدد المهام الحقيقي للبرمجة غير المتزامنة.
[1] : شفرة المصدر الكاملة متاحة
هنا .
[2] : المقال الأصلي
"وظيفة المتزامن مقابل دالة تُرجع وعدًا ، وترجمة المقال
"الفرق بين دالة غير متزامنة ووظيفة تُرجع وعدًا .
"[3] : يمكنك القول إن جافا سكريبت يمكنها تحديد نوع المزامنة / الانتظار من مجموعة الوظائف والتحقق بشكل متكرر ، ولكن لم يتم تصميم JavaScript لرعاية سلامة النوع الثابت في وقت الترجمة ، ناهيك إنه أكثر ملاءمة للمطورين لمعرفة نوع الوظيفة بوضوح.
[4] : كتبت وظائف "غير متزامنة" ، بافتراض أنها تعمل تحت نفس الواجهة مثل
setTimeout
. لاحظ أن عمليات الاسترجاعات غير متوافقة مع الوعود والعكس.