محركات جافا سكريبت: كيف تعمل؟ من مكدس الاتصال إلى الوعود ، (تقريبًا) كل ما تحتاج إلى معرفته


هل تساءلت يومًا عن كيفية قراءة المتصفحات وتنفيذها لرمز JavaScript؟ تبدو غامضة ، لكن في هذا المنشور يمكنك الحصول على فكرة عما يحدث تحت الغطاء.

نبدأ رحلتنا إلى اللغة برحلة إلى العالم الرائع لمحركات JavaScript.

افتح وحدة التحكم في Chrome وانتقل إلى علامة التبويب مصادر. سترى عدة أقسام ، أحد أكثرها إثارة للاهتمام يسمى Call Stack (في Firefox سترى Call Stack عندما تضع نقطة توقف في الكود):



ما هي مكدس المكالمات؟ يبدو أن هناك الكثير يحدث هنا ، حتى من أجل تنفيذ سطرين من التعليمات البرمجية. في الواقع ، لا يأتي JavaScript في صندوق به كل متصفح. هناك مكون كبير يقوم بترجمة وتفسير رمز JavaScript الخاص بنا - إنه محرك JavaScript. الأكثر شعبية هي V8 ، يتم استخدامه في جوجل كروم و Node.js ، SpiderMonkey في Firefox ، JavaScriptCore في Safari / WebKit.

تعد محركات JavaScript اليوم أمثلة رائعة على هندسة البرمجيات ، وسيكون من شبه المستحيل التحدث عن جميع الجوانب. ومع ذلك ، فإن العمل الرئيسي في تنفيذ التعليمات البرمجية يتم من خلال عدد قليل فقط من مكونات المحركات: Call Stack (استدعاء المكدس) ، الذاكرة العالمية (الذاكرة العالمية) وسياق التنفيذ (سياق التنفيذ). هل أنت مستعد للقاءهم؟

المحتويات:

  1. محركات جافا سكريبت والذاكرة العالمية
  2. محركات جافا سكريبت: كيف تعمل؟ سياق التنفيذ العام واستدعاء المكدس
  3. JavaScript عبارة عن قصص مفردة وقصص ممتعة أخرى
  4. جافا سكريبت غير متزامن ، قائمة انتظار رد الاتصال وحلقة الحدث
  5. رد الجحيم وعود ES6
  6. إنشاء والعمل مع وعود JavaScript
  7. خطأ في معالجة وعود ES6
  8. موحدات وعد ES6: Promise.all ، Promise.allSettled ، Promise.any وغيرها
  9. ES6 وعود وقائمة microtask
  10. محركات جافا سكريبت: كيف تعمل؟ تطور غير متزامن: من الوعود إلى عدم التزامن / الانتظار
  11. محركات جافا سكريبت: كيف تعمل؟ النتائج

1. محركات جافا سكريبت والذاكرة العالمية


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

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

للبدء ، انظر إلى هذا الكود:

var num = 2; function pow(num) { return num * num; } 

افترض أنني أسألك كيف تتم معالجة هذا الرمز في المتصفح؟ ماذا سوف تجيب؟ يمكنك القول: "المستعرض يقرأ الكود" أو "المستعرض ينفذ الكود". في الواقع ، كل شيء ليس بهذه البساطة. أولاً ، لا تتم قراءة الرمز بواسطة المتصفح ، ولكن بواسطة المحرك. يقرأ محرك جافا سكريبت الكود ، وبمجرد أن يحدد السطر الأول ، فإنه يضع بضع روابط في الذاكرة العالمية .

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



حتى لو كان المثال يحتوي فقط على متغير ووظيفة ، تخيل أنه يتم تنفيذ شفرة JavaScript في بيئة أكبر: في مستعرض أو في Node.js. في مثل هذه البيئات ، هناك العديد من الوظائف والمتغيرات المحددة مسبقًا والتي تسمى Global. لذلك ، ستحتوي الذاكرة العالمية على بيانات أكثر بكثير من مجرد pow ، مع الأخذ في الاعتبار.

لا شيء يعمل في الوقت الحالي. دعنا الآن نحاول تنفيذ مهمتنا:

 var num = 2; function pow(num) { return num * num; } pow(num); 

ماذا سيحدث؟ وسوف يحدث شيء مثير للاهتمام. عند استدعاء الوظيفة ، سيقوم محرك JavaScript بتسليط الضوء على قسمين:

  • سياق التنفيذ العالمي
  • استدعاء المكدس

ما هم؟

2. محركات جافا سكريبت: كيف تعمل؟ سياق التنفيذ العام واستدعاء المكدس


تعلمت كيف يقرأ محرك JavaScript المتغيرات وإعلانات الوظائف. أنها تقع في الذاكرة العالمية (كومة).

ولكننا ننفذ الآن وظيفة جافا سكريبت ، ويجب على المحرك الاهتمام بهذا. كيف؟ يحتوي كل محرك JavaScript على مكون رئيسي يسمى مكدس الاستدعاءات .

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

لكن العودة إلى مثالنا. عندما يتم استدعاء وظيفة ، يرسلها المحرك إلى مكدس الاستدعاءات :



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

في الوقت نفسه ، يضع المحرك سياق التنفيذ العام في الذاكرة ، هذه هي البيئة العامة التي يتم فيها تنفيذ تعليمات JavaScript البرمجية. إليك ما يبدو عليه:



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

حتى في الحالة البسيطة ، كما هو موضح أدناه ، ينشئ محرك JavaScript سياق تنفيذ محلي :

 var num = 2; function pow(num) { var fixed = 89; return num * num; } pow(num); 

لاحظ أنني أضفت المتغير fixed إلى وظيفة pow . في هذه الحالة ، سوف يحتوي سياق التنفيذ المحلي على قسم fixed . لست جيدًا في رسم مستطيلات صغيرة داخل مستطيلات صغيرة أخرى ، لذا استخدم خيالك.

سيظهر سياق التنفيذ المحلي بجوار pow داخل قسم المستطيل الأخضر الموجود داخل سياق التنفيذ العالمي. تخيل أيضًا كيف أن كل دالة متداخلة داخل الوظيفة المتداخلة ، يقوم المحرك بإنشاء سياقات تنفيذ محلية أخرى. كل هذه المقاطع المستطيل تظهر بسرعة كبيرة! مثل دمية التعشيش!

دعنا نعود إلى قصة واحدة الخيوط. ماذا يعني هذا؟

3. جافا سكريبت هي مترابطة واحدة ، وقصص ممتعة أخرى


نقول إن جافا سكريبت JavaScript ذات ترابط واحد لأن مكدس مكالمة واحدة فقط يعالج وظائفنا . اسمحوا لي أن أذكرك بأن الوظائف لا يمكن أن تترك مكدس الاستدعاء إذا كانت الوظائف الأخرى تتوقع التنفيذ.

هذه ليست مشكلة إذا كنا نعمل مع رمز متزامن. على سبيل المثال ، إضافة رقمين متزامن ويتم حسابها بالميكرو ثانية. ماذا عن مكالمات الشبكة والتفاعلات الأخرى مع العالم الخارجي؟

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

في الوقت نفسه ، أنت تعلم أنه عندما يقوم المتصفح بتحميل نوع من تعليمات JavaScript البرمجية ، يقوم المحرك بقراءة هذا الرمز بسطر ويقوم بالخطوات التالية:

  • يضع المتغيرات والتصريحات الدالة في الذاكرة العامة (كومة الذاكرة المؤقتة).
  • يرسل مكالمة إلى كل وظيفة في مكدس المكالمة.
  • ينشئ سياق تنفيذ عام يتم فيه تنفيذ الوظائف العامة.
  • ينشئ العديد من سياقات التنفيذ المحلية الصغيرة (إذا كانت هناك متغيرات داخلية أو وظائف متداخلة).

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

4. JavaScript غير متزامن ، قائمة انتظار رد الاتصال ، وحلقة حدث


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

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

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

 setTimeout(callback, 10000); function callback(){ console.log('hello timer!'); } 

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

بعد 10 ثوانٍ ، يأخذ المستعرض وظيفة رد الاتصال التي انتقلنا إليها ووضعها في قائمة انتظار رد الاتصال . في الوقت الحالي ، ظهر قسمان مستطيلان آخران في محرك JavaScript. ألقِ نظرة على هذا الكود:

 var num = 2; function pow(num) { return num * num; } pow(num); setTimeout(callback, 10000); function callback(){ console.log('hello timer!'); } 

الآن مخططنا يشبه هذا:



يتم تنفيذ setTimeout داخل سياق المتصفح. بعد 10 ثوانٍ ، يبدأ الموقت وتكون وظيفة رد الاتصال جاهزة للتنفيذ. ولكن أولاً ، يجب أن تمر قائمة انتظار رد الاتصال. هذا عبارة عن بنية بيانات في شكل قائمة انتظار ، وكما يشير اسمها ، قائمة انتظار مرتبة من الوظائف.

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

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

بعد ذلك ، تعتبر وظيفة تنفيذها. هذا هو المخطط العام لمعالجة التعليمات البرمجية غير المتزامنة والمتزامنة مع محرك JavaScript:



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

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

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

لكننا لم ننته من موضوع JavaScript غير المتزامن حتى الآن. في الفصول التالية سننظر في وعود ES6.

5. الجحيم رد و ES6 وعود


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

 function mapper(element){ return element * 2; } [1, 2, 3, 4, 5].map(mapper); 

mapper هو وظيفة رد الاتصال التي يتم تمريرها داخل map . الكود أعلاه متزامن. الآن النظر في هذا الفاصل الزمني:

 function runMeEvery(){ console.log('Ran!'); } setInterval(runMeEvery, 5000); 

هذا الرمز غير متزامن ، لأنه داخل setInterval نقوم بتمرير رد الاتصال runMeEvery. يتم استخدام عمليات الاسترجاعات عبر جافا سكريبت ، لذا واجهنا لسنوات مشكلة تسمى "رد اتصال الجحيم" - "رد اتصال الجحيم".

يتم تطبيق مصطلح Callback hell في JavaScript على "نمط" البرمجة حيث يتم تضمين callbacks في عمليات الاسترجاعات الأخرى المضمنة في عمليات الاستدعاء الأخرى ... نظرًا للطبيعة غير المتزامنة ، فإن مبرمجي JavaScript قد سقطوا لفترة طويلة في هذا الفخ.

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

لن أتحدث بالتفصيل عن معاودة الاتصال بالجحيم ، إذا كنت مهتمًا ، فانتقل إلى callbackhell.com ، حيث تم التحقيق في هذه المشكلة بالتفصيل وتم اقتراح حلول متعددة. وسوف نتحدث عن وعود ES6 . هذا هو ملحق جافا سكريبت مصممة لحل مشكلة معاودة الاتصال الجحيم. ولكن ما هي الوعود؟

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

6. إنشاء والعمل مع وعود JavaScript


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

 const myPromise = new Promise(function(resolve){ setTimeout(function(){ resolve() }, 5000) }); 

كما ترون ، resolve هو وظيفة نسميها حتى ينتهي الوعد بنجاح. والرفض سيخلق وعدًا مرفوضًا:

 const myPromise = new Promise(function(resolve, reject){ setTimeout(function(){ reject() }, 5000) }); 

لاحظ أنه يمكنك تجاهل reject لأن هذه هي المعلمة الثانية. ولكن إذا كنت تنوي استخدام reject ، فلا يمكنك تجاهل resolve . أي أن الكود التالي لن يعمل وسينتهي بوعد مسموح به:

 // Can't omit resolve ! const myPromise = new Promise(function(reject){ setTimeout(function(){ reject() }, 5000) }); 

الوعود لا تبدو مفيدة الآن ، أليس كذلك؟ هذه الأمثلة لا تعرض أي شيء للمستخدم. دعنا نضيف شيئا. والوعود المرفوضة يمكن أن تُرجع البيانات. على سبيل المثال:

 const myPromise = new Promise(function(resolve) { resolve([{ name: "Chris" }]); }); 

لكن ما زلنا لا نرى أي شيء. لاستخراج البيانات من الوعد ، تحتاج إلى ربط الوعد بالطريقة then . يأخذ رد الاتصال (ما هي السخرية!) ، والذي يتلقى البيانات الحالية:

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then(function(data) { console.log(data); }); 

كمطور جافا سكريبت والمستهلك لرمز الآخرين ، تتفاعل في الغالب مع الوعود الخارجية. غالبًا ما يلتف منشئو المكتبات بالكود القديم في مُنشئ الوعد ، مثل هذا:

 const shinyNewUtil = new Promise(function(resolve, reject) { // do stuff and resolve // or reject }); 

وإذا لزم الأمر ، يمكننا أيضًا إنشاء وعد Promise.resolve() عن طريق الاتصال بـ Promise.resolve() :

 Promise.resolve({ msg: 'Resolve!'}) .then(msg => console.log(msg)); 

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

7. خطأ في معالجة وعود ES6


كانت معالجة الأخطاء في JavaScript سهلة دائمًا ، على الأقل في الشفرة المتزامنة. ألقِ نظرة على مثال:

 function makeAnError() { throw Error("Sorry mate!"); } try { makeAnError(); } catch (error) { console.log("Catching the error! " + error); } 

ستكون النتيجة:

 Catching the error! Error: Sorry mate! 

كما هو متوقع ، وقع الخطأ في catch . جرب الآن الوظيفة غير المتزامنة:

 function makeAnError() { throw Error("Sorry mate!"); } try { setTimeout(makeAnError, 5000); } catch (error) { console.log("Catching the error! " + error); } 

هذا الرمز غير متزامن بسبب setTimeout . ماذا سيحدث إذا قمنا بتنفيذها؟

  throw Error("Sorry mate!"); ^ Error: Sorry mate! at Timeout.makeAnError [as _onTimeout] (/home/valentino/Code/piccolo-javascript/async.js:2:9) 

الآن النتيجة مختلفة. لم يتم اكتشاف الخطأ من قبل catch ، ولكن ارتفع المكدس بحرية. السبب هو أن try/catch يعمل فقط مع رمز متزامن. إذا كنت تريد معرفة المزيد ، فستتم مناقشة هذه المشكلة بالتفصيل هنا .

لحسن الحظ ، مع الوعود ، يمكننا التعامل مع الأخطاء غير المتزامنة كما لو كانت متزامنة. في الفصل الأخير ، قلت إن استدعاء reject يؤدي إلى رفض الوعد:

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); 

في هذه الحالة ، يمكننا معالجة الأخطاء باستخدام معالج catch عن طريق سحب (مرة أخرى) رد اتصال:

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); myPromise.catch(err => console.log(err)); 

بالإضافة إلى ذلك ، لإنشاء وعد في المكان المناسب Promise.reject() ، يمكنك الاتصال بـ Promise.reject() :

 Promise.reject({msg: 'Rejected!'}).catch(err => console.log(err)); 

واسمحوا لي أن أذكرك: يتم تنفيذ المعالج then عند تنفيذ الوعد ، ويتم تنفيذ معالج الاستيلاء للوعود المرفوضة. لكن هذه ليست نهاية القصة. أدناه سنرى كيف تعمل async/await بشكل رائع مع try/catch .

8. توليفات ES6 الوعود: Promise.all ، Promise.allSettled ، Promise.any وغيرها


الوعود ليست مصممة للعمل وحدها. يقدم Promise API عددًا من الطرق لدمج الوعود . يعد Promise.all أحد أكثر البرامج المفيدة ، فهو يتطلب مجموعة من الوعود وإرجاع وعد واحد. المشكلة الوحيدة هي أنه يتم رفض Promise.all إذا تم رفض وعد واحد على الأقل في الصفيف.

يسمح Promise.race أو يرفض بمجرد تلقي أحد الوعود في الصفيف الحالة المقابلة.

في الإصدارات الأحدث من V8 ، سيتم أيضًا تقديم Promise.allSettled : Promise.allSettled و Promise.any . Promise.any لا يزال في مرحلة مبكرة من الوظيفة المقترحة ، في وقت كتابة هذا المقال غير معتمد. ومع ذلك ، من الناحية النظرية ، سيكون قادرًا على الإشارة إلى ما إذا كان قد تم تنفيذ أي وعد. الفرق من Promise.race هو أن Promise.any غير مرفوض ، حتى إذا تم رفض أحد الوعود .

Promise.allSettled أكثر إثارة للاهتمام. يأخذ أيضًا مجموعة من الوعود ، لكنه لا "يختصر" إذا تم رفض أحد الوعود. إنه مفيد عندما تحتاج إلى التحقق مما إذا كانت جميع الوعود في صفيف قد انتقلت إلى مرحلة ما ، بغض النظر عن وجود الوعود المرفوضة. يمكن اعتبار عكس Promise.all .

9. ES6 وعود وقائمة microtask


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

وهنا تحتاج إلى توخي الحذر: قائمة انتظار microtask تسبق قائمة انتظار المكالمة . عمليات الاسترجاعات من قائمة انتظار microtask لها الأسبقية عندما تتحقق حلقة الحدث لمعرفة ما إذا كانت عمليات الاسترجاعات الجديدة جاهزة للعمل في مكدس الاستدعاءات.

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

10. محركات جافا سكريبت: كيف تعمل؟ تطور غير متزامن: من الوعود إلى عدم التزامن / الانتظار


جافا سكريبت تتطور بسرعة ونحن نعمل باستمرار على تحسين كل عام. بدت الوعود وكأنها نهاية ، ولكن مع ECMAScript 2017 (ES8) ، ظهر بناء جملة جديد: async/await .

async/await هو مجرد تحسين الأسلوبية التي نسميها السكر النحوي. لا يؤدي تغيير async/await تغيير جافا سكريبت بأي طريقة (لا تنس أن اللغة يجب أن تكون متوافقة مع الإصدارات السابقة مع المتصفحات الأقدم ويجب عدم كسر الشفرة الحالية). هذه مجرد طريقة جديدة لكتابة رمز غير متزامن بناءً على الوعود. النظر في مثال. أعلاه ، لقد أنقذنا الوعد بالفعل في المقابلة then :

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); myPromise.then((data) => console.log(data)) 

الآن مع async/await يمكننا معالجة الكود غير المتزامن حتى يصبح القارئ متزامنًا لقارئ قائمتنا . بدلاً من استخدام then يمكننا أن نلتزم بالوعد في وظيفة تسمى غير async ، ومن ثم await النتيجة:

 const myPromise = new Promise(function(resolve, reject) { resolve([{ name: "Chris" }]); }); async function getData() { const data = await myPromise; console.log(data); } getData(); 

تبدو جيدة ، أليس كذلك؟ من المضحك أن وظيفة المزامنة تُرجع دائمًا وعدًا ، ولا يمكن لأحد أن يمنعها من القيام بذلك:

 async function getData() { const data = await myPromise; return data; } getData().then(data => console.log(data)); 

ماذا عن الاخطاء؟ واحدة من مزايا async/await أن هذا البناء يمكن أن يسمح لنا باستخدام try/catch . اقرأ المقدمة لمعالجة الأخطاء في وظائف المزامنة واختبارها .

دعونا نلقي نظرة على الوعد مرة أخرى ، حيث نتعامل مع الأخطاء مع معالج catch :

 const myPromise = new Promise(function(resolve, reject) { reject('Errored, sorry!'); }); myPromise.catch(err => console.log(err)); 

مع وظائف غير متزامنة ، يمكننا refactor مثل هذا:

 async function getData() { try { const data = await myPromise; console.log(data); // or return the data with return data } catch (error) { console.log(error); } } getData(); 

ومع ذلك ، لم يتحول الجميع إلى هذا النمط. try/catch يمكن أن تعقد التعليمات البرمجية الخاصة بك. هناك شيء آخر يجب مراعاته. تعرف على كيفية حدوث خطأ داخل كتلة try في هذا الرمز:

 async function getData() { try { if (true) { throw Error("Catch me if you can"); } } catch (err) { console.log(err.message); } } getData() .then(() => console.log("I will run no matter what!")) .catch(() => console.log("Catching err")); 

ماذا عن الخطين اللذين يتم عرضهما في وحدة التحكم؟ تذكر أن try/catch عبارة عن بنية متزامنة ، ووظيفتنا غير المتزامنة تولِّد وعدًا . تتبع طريقين مختلفين ، مثل القطارات. ! , throw , catch getData() . , «Catch me if you can», «I will run no matter what!».

, throw then . , , Promise.reject() :

 async function getData() { try { if (true) { return Promise.reject("Catch me if you can"); } } catch (err) { console.log(err.message); } } Now the error will be handled as expected: getData() .then(() => console.log("I will NOT run no matter what!")) .catch(() => console.log("Catching err")); "Catching err" // output 

async/await JavaScript. .

, JS- async/await . . , async/await — .

11. JavaScript-: ?


JavaScript — , , . JS-: V8, Google Chrome Node.js; SpiderMonkey, Firefox; JavaScriptCore, Safari.

JavaScript- «» : , , , . , .

JavaScript- , . JavaScript: , - , (, ) .

ECMAScript 2015 . — , . . 2017- async/await : , , .

Source: https://habr.com/ru/post/ar452906/


All Articles