اختار المؤلف صندوق تخفيف فيروس كورونا 19 لتلقي تبرع كجزء من برنامج الكتابة من أجل التبرعات.
المقدمة
في بدايات الإنترنت، كانت المواقع عادة تتألف من بيانات ثابتة في صفحة HTML. ولكن الآن، مع تزايد تفاعل تطبيقات الويب ودينامية، أصبح من الضروري بشكل متزايد القيام بعمليات مكثفة مثل إجراء طلبات شبكة خارجية لاسترداد بيانات واجهة برمجة التطبيقات (API). للتعامل مع هذه العمليات في JavaScript، يجب على المطور استخدام تقنيات البرمجة الغير متزامنة.
نظرًا لأن JavaScript هو لغة برمجة أحادية الموضوع مع نموذج تنفيذ متزامن يعالج عملية واحدة بعد الأخرى، فإنه يمكنه معالجة عبارة واحدة في كل مرة. ومع ذلك، فإن إجراء مثل طلب البيانات من API يمكن أن يستغرق مقدارًا غير محدد من الوقت، اعتمادًا على حجم البيانات المطلوبة، وسرعة اتصال الشبكة، وعوامل أخرى. إذا تمت عمليات استدعاء API بطريقة متزامنة، فإن المتصفح لن يتمكن من التعامل مع أي مدخلات مستخدم، مثل التمرير أو النقر على زر، حتى تكتمل تلك العملية. وهذا ما يعرف بـ الحجب.
لمنع التصرفات القفلية، تحتوي بيئة المتصفح على العديد من واجهات برمجة التطبيقات الويب التي يمكن لجافا سكريبت الوصول إليها والتي تكون غير متزامنة، مما يعني أنها يمكن أن تعمل بشكل متواز مع العمليات الأخرى بدلاً من التتابعي. وهذا مفيد لأنه يسمح للمستخدم بالاستمرار في استخدام المتصفح بشكل طبيعي بينما تتم معالجة العمليات غير المتزامنة.
كمطور جافا سكريبت، تحتاج إلى معرفة كيفية العمل مع واجهات برمجة التطبيقات الويب غير المتزامنة ومعالجة الاستجابة أو الخطأ الناتج عن تلك العمليات. في هذا المقال، ستتعرف على حلقة الأحداث، والطريقة الأصلية للتعامل مع السلوك غير المتزامن من خلال التعامل مع التعهدات التي تمت إضافتها في ECMAScript 2015، والممارسة الحديثة لاستخدام async/await
.
ملاحظة: يركز هذا المقال على جافا سكريبت الجانب العميل في بيئة المتصفح. تكون نفس المفاهيم عمومًا صحيحة في بيئة Node.js، ومع ذلك، يستخدم Node.js واجهات برمجة التطبيقات الخاصة به والتي تعتمد على واجهات برمجة التطبيقات C++ بدلاً من واجهات برمجة التطبيقات الويب للمتصفح. لمزيد من المعلومات حول البرمجة غير المتزامنة في Node.js، تفضل بزيارة كيفية كتابة كود غير متزامن في Node.js.
حلقة الأحداث
سيقوم هذا القسم بشرح كيفية تعامل JavaScript مع الشفرة الغير متزامنة باستخدام حلقة الأحداث. سيتم تشغيل عرض توضيحي أولاً لحلقة الأحداث في العمل، ثم سيتم شرح عنصري حلقة الأحداث: الكومة والطابور.
سيقوم الشفرة JavaScript التي لا تستخدم أي واجهات برمجة تطبيقات ويب غير متزامنة بالتنفيذ بطريقة متزامنة – واحدة في كل مرة، تتابعيًا. يُظهر ذلك من خلال الشفرة المثالية التالية التي تقوم بالاتصال بثلاث دوال تقوم كل منها بطباعة رقم على الconsole:
في هذا الكود، قمت بتعريف ثلاث دوال تقوم بطباعة أرقام باستخدام console.log()
.
ثم، قم بكتابة استدعاء للدوال:
سيتم الإخراج بناءً على ترتيب استدعاء الدوال – first()
، second()
، ثم third()
:
Output1
2
3
عند استخدام واجهة برمجة تطبيقات ويب غير متزامنة، تصبح القواعد أكثر تعقيدا. واحدة من الواجهات البرمجية المدمجة التي يمكنك اختبار ذلك بها هي setTimeout
، التي تقوم بتعيين مؤقت وتنفذ إجراء بعد فترة زمنية محددة. setTimeout
يحتاج إلى أن يكون غير متزامن، وإلا ستظل المتصفح بأكمله متجمدًا أثناء الانتظار، مما سيؤدي إلى تجربة مستخدم سيئة.
أضف setTimeout
إلى الدالة second
لتحاكي طلبًا غير متزامناً:
setTimeout
يأخذ معه وسيطين: الدالة التي سيقوم بتشغيلها بشكل غير متزامن، والمدة التي سينتظرها قبل استدعاء تلك الدالة. في هذا الكود قمت بتضمين console.log
داخل دالة مجهولة ومررتها إلى setTimeout
، ثم قمت بتعيين تشغيل الدالة بعد 0
ميلي ثانية.
الآن قم باستدعاء الدوال، كما فعلت من قبل:
قد تتوقع أنه مع setTimeout
المعد لـ 0
ثانية أن تقوم تشغيل هذه الدوال الثلاثة لا يزال سينتج في طباعة الأرقام بتتابع. ولكن بسبب أنها غير متزامنة، سيتم طباعة الدالة ذات المهلة في النهاية:
Output1
3
2
سواء قمت بتعيين المهلة إلى ثانية صفر أو خمس دقائق لن يحدث أي فرق – ستقوم console.log
المدعوة بالتعليمات البرمجية غير المتزامنة بالتنفيذ بعد الدوال الطبقية الأعلى. يحدث هذا لأن بيئة تشغيل JavaScript، في هذه الحالة المتصفح، تستخدم مفهوم الحلقة الحدثية للتعامل مع التنافر أو الأحداث المتزامنة. نظرًا لأن JavaScript يمكن أن يقوم بتنفيذ تعليمة واحدة في كل مرة، فإنه يحتاج إلى إبلاغ الحلقة الحدثية عند تنفيذ أي تعليمة محددة بالتحديد. تتعامل الحلقة الحدثية مع هذا باستخدام مفاهيم الـ حد العلوي و قائمة الانتظار.
الحد العلوي
الــstack، أو الكومة الفرعية، تحتوي على حالة الدالة التي تعمل حاليًا. إذا كنت غير ملم بمفهوم الكومة، يمكنك تخيلها كـمصفوفة تتبع خصائص “آخر داخل، أول خارج” (LIFO)، مما يعني أنه يمكنك فقط إضافة أو إزالة العناصر من نهاية الكومة. تقوم جافا سكريبت بتشغيل الـframe الحالي (أو استدعاء الدالة في بيئة محددة) في الكومة، ثم تقوم بإزالته والانتقال إلى الدالة التالية.
فيما يتعلق بالمثال الذي يحتوي فقط على رمز تزامني، يتولى المتصفح التنفيذ بالترتيب التالي:
- إضافة
first()
إلى الكومة، تشغيلfirst()
الذي يسجل1
في وحدة التحكم، إزالةfirst()
من الكومة. - إضافة
second()
إلى الكومة، تشغيلsecond()
الذي يسجل2
في وحدة التحكم، إزالةsecond()
من الكومة. - إضافة
third()
إلى الكومة، تشغيلthird()
الذي يسجل3
في وحدة التحكم، إزالةthird()
من الكومة.
المثال الثاني باستخدام setTimout
يبدو مثل هذا:
- إضافة
first()
إلى الكومة، تشغيلfirst()
الذي يسجل1
في وحدة التحكم، إزالةfirst()
من الكومة. - إضافة
second()
إلى الكومة، تشغيلsecond()
.- إضافة
setTimeout()
إلى الكومة، تشغيل واجهة البرمجة الويبية لـsetTimeout()
التي تبدأ مؤقتًا وتضيف الدالة المجهولة إلى الـqueue، ثم إزالةsetTimeout()
من الكومة.
- إضافة
- احذف
second()
من الكومة. - أضف
third()
إلى الكومة، قم بتشغيلthird()
الذي يسجل3
في وحدة التحكم، واحذفthird()
من الكومة. - حلقة الأحداث تفحص الطابور لأي رسائل معلقة وتجد الدالة المجهولة من
setTimeout()
، وتضيف الدالة إلى الكومة التي تسجل2
في وحدة التحكم، ثم تقوم بإزالتها من الكومة.
باستخدام setTimeout
، واجهة برمجة التطبيقات الويبية غير المتزامنة، يتم تقديم مفهوم الطابور، الذي ستغطيه الدورة التعليمية التالية.
الطابور
الطابور، المعروف أيضًا بالرسائل أو طابور المهام، هو منطقة انتظار للدوال. في كل مرة تكون فيها الكومة فارغة، ستقوم حلقة الأحداث بفحص الطابور لأي رسائل في انتظار، بدءًا من أقدم رسالة. عندما تجد واحدة، ستقوم بإضافتها إلى الكومة، حيث ستقوم بتنفيذ الدالة في الرسالة.
في مثال setTimeout
، يتم تشغيل الدالة المجهولة فوراً بعد انتهاء تنفيذ المستوى العلوي الباقي، حيث تم تعيين المؤقت إلى 0
ثانية. من المهم أن نتذكر أن المؤقت لا يعني أن الكود سيُنفذ بالضبط بعد 0
ثانية أو بعد الوقت المحدد، ولكن أنه سيقوم بإضافة الدالة المجهولة إلى قائمة الانتظار في تلك الفترة الزمنية. هذا النظام للقوائم المنتظمة موجود لأنه إذا كان المؤقت سيقوم بإضافة الدالة المجهولة مباشرةً إلى الكومة عندما ينتهي المؤقت، فإنه سيقاطع أي دالة تعمل حاليًا، مما قد يؤدي إلى آثار غير مقصودة وغير متوقعة.
ملاحظة: هناك أيضًا قائمة انتظار أخرى تُسمى قائمة المهام أو قائمة المهام الصغيرة التي تتعامل مع الوعود. المهام الصغيرة مثل الوعود تُعالج بأولوية أعلى من المهام الكبيرة مثل setTimeout
.
الآن تعرف كيف يستخدم الحلقة الحدثية الكومة والقائمة للتحكم في ترتيب تنفيذ الكود. المهمة التالية هي معرفة كيفية التحكم في ترتيب التنفيذ في كودك. للقيام بذلك، ستتعلم أولاً عن الطريقة الأصلية لضمان معالجة الكود غير المتزامن بشكل صحيح من قبل الحلقة الحدثية: دوال الارتجاع.
دوال الارتجاع
في مثال setTimeout
، تم تشغيل الوظيفة مع المهلة الزمنية بعد كل شيء في سياق التنفيذ الرئيسي على مستوى الأعلى. ولكن إذا أردت التأكد من تشغيل إحدى الوظائف، مثل الوظيفة third
، بعد انتهاء المهلة الزمنية، فيجب عليك استخدام أساليب البرمجة الغير متزامنة. يمكن أن تمثل المهلة الزمنية هنا استدعاء واجهة برمجة تطبيقات غير متزامن يحتوي على بيانات. تريد العمل مع البيانات من استدعاء واجهة البرمجة التطبيقية، ولكن عليك التأكد من أن البيانات تمت إرجاعها أولاً.
الحل الأصلي للتعامل مع هذه المشكلة هو استخدام دوال الاستدعاء. لدوال الاستدعاء ليس لديها بنية بيانات خاصة؛ فهي مجرد دالة تم تمريرها كوسيطة إلى دالة أخرى. الدالة التي تأخذ دالة أخرى كوسيطة تسمى دالة عالية الترتيب. وفقًا لهذا التعريف، يمكن لأي دالة أن تصبح دالة استدعاء إذا تم تمريرها كوسيطة. الدوال الاستدعاء ليست غير متزامنة بطبيعتها، ولكن يمكن استخدامها لأغراض غير متزامنة.
إليك مثالًا على كود بناء دالة عالية الترتيب ودالة استدعاء بصيغة بنائية:
في هذا الكود، تعرّف على دالة fn
، وتعرّف على دالة higherOrderFunction
التي تأخذ دالة callback
كوسيطة، وتمرر fn
كدالة استدعاء إلى higherOrderFunction
.
تشغيل هذا الكود سيعطي النتائج التالية:
OutputJust a function
لنعود إلى الدوال الأولى
، الثانية
، و الثالثة
باستخدام setTimeout
. هذا ما لديك حتى الآن:
المهمة هي الحصول على الدالة الثالثة
لتأخير التنفيذ دائمًا حتى بعد اكتمال الإجراء غير المتزامن في الدالة الثانية
. هنا يأتي دور مراجع الاستدعاء. بدلاً من تنفيذ الأولى
، الثانية
، و الثالثة
في السطح العلوي للتنفيذ، ستمرر الدالة الثالثة
كوسيط إلى الثانية
. ستقوم الدالة الثانية
بتنفيذ مراجع الاستدعاء بعد اكتمال الإجراء غير المتزامن.
إليك الدوال الثلاث مع تطبيق مراجع الاستدعاء:
الآن، قم بتنفيذ الأولى
و الثانية
، ثم قم بتمرير الثالثة
كوسيط إلى الثانية
:
بعد تشغيل هذا الكود، ستحصل على الناتج التالي:
Output1
2
3
سيتم طباعة 1
أولاً، وبعد اكتمال المؤقت (في هذه الحالة، صفر ثوانٍ، ولكن يمكنك تغييره إلى أي مدة) سيتم طباعة 2
ثم 3
. عن طريق تمرير دالة كمرجع استدعاء، لقد نجحت في تأخير تنفيذ الدالة حتى يكتمل الإجراء غير المتزامن من واجهة برمجة التطبيقات عبر الويب (setTimeout
).
أهم ما يمكن استخلاصه هو أن وظائف الاستدعاء ليست غير متزامنة – setTimeout هو واجهة برمجة تطبيقات الويب غير المتزامنة المسؤولة عن معالجة المهام غير المتزامنة. يسمح الاستدعاء فقط لك بأن تكون معلمًا عند اكتمال مهمة غير متزامنة ويتولى نجاح أو فشل المهمة.
الآن بعد أن تعلمت كيفية استخدام وظائف الاستدعاء للتعامل مع المهام غير المتزامنة، يشرح القسم التالي مشاكل تضمين العديد من وظائف الاستدعاء وإنشاء “هرم القدرة”.
الوظائف المتداخلة وهرم القدرة
وظائف الاستدعاء هي طريقة فعالة لضمان تنفيذ تأخير لوظيفة حتى تكتمل واحدة أخرى وتعود بالبيانات. ومع ذلك، بسبب الطبيعة المتداخلة لوظائف الاستدعاء، يمكن أن يتسبب الكود في فوضى إذا كان هناك الكثير من الطلبات غير المتزامنة المتتالية التي تعتمد على بعضها البعض. كانت هذه مشكلة كبيرة لمطوري JavaScript في وقت مبكر، ونتيجة لذلك يُطلق على الكود الذي يحتوي على وظائف استدعاء متداخلة في كثير من الأحيان “هرم القدرة” أو “جحيم الاستدعاء”.
إليك عرضًا لوظائف الاستدعاء المتداخلة:
في هذا الكود، تكون كل عملية setTimeout الجديدة متضمنة داخل دالة أعلى الطلب، مما يخلق شكلًا هرميًا من الوظائف المتداخلة الأعمق والأعمق. تشغيل هذا الكود سيعطي النتيجة التالية:
Output1
2
3
عمليًا، مع كود غير متزامن في العالم الحقيقي، يمكن أن يصبح الأمر أكثر تعقيدًا بكثير. من المحتمل بشدة أن تحتاج إلى التعامل مع الأخطاء في الكود غير المتزامن، ثم تمرير بعض البيانات من كل استجابة إلى الطلب التالي. فعل هذا باستخدام التعابير الدالة سيجعل كودك صعب القراءة والصيانة.
إليك مثالًا قابلًا للتشغيل على “هرم الهلاك” الأكثر واقعية يمكنك تجربته:
في هذا الكود، يجب على كل دالة أن تأخذ في الاعتبار استجابة محتملة وخطأ محتمل، مما يجعل دالة “callbackHell” مربكة بصرياً.
تشغيل هذا الكود سيمنحك النتائج التالية:
Output
First 9
Second 3
Error: Whoa! Something went wrong.
at asynchronousRequest (<anonymous>:4:21)
at second (<anonymous>:29:7)
at <anonymous>:9:13
هذه الطريقة في التعامل مع الكود غير المتزامن صعبة المتابعة. ونتيجة لذلك، تم تقديم مفهوم الوعود في ES6. وهذا هو محور القسم التالي.
الوعود
A promise represents the completion of an asynchronous function. It is an object that might return a value in the future. It accomplishes the same basic goal as a callback function, but with many additional features and a more readable syntax. As a JavaScript developer, you will likely spend more time consuming promises than creating them, as it is usually asynchronous Web APIs that return a promise for the developer to consume. This tutorial will show you how to do both.
إنشاء وعد
يمكنك تهيئة وعد باستخدام بنية new Promise
، ويجب عليك تهيئته بوظيفة. تتلقى الوظيفة التي يتم تمريرها إلى وعد معلمات resolve
و reject
. تتولى وظائف resolve
و reject
معالجة نجاح وفشل العملية، على التوالي.
اكتب السطر التالي لتعريف وعد:
إذا قمت بتفحص الوعد المهيأ بهذه الحالة باستخدام وحدة التحكم في المتصفح الخاص بك، ستجد أنه لديه حالة pending
وقيمة undefined
:
Output__proto__: Promise
[[PromiseStatus]]: "pending"
[[PromiseValue]]: undefined
حتى الآن، لم يتم إعداد أي شيء للوعد، لذلك سيبقى في حالة pending
إلى الأبد. أول شيء يمكنك القيام به لاختبار الوعد هو إكمال الوعد بتحديده بقيمة:
الآن، عند فحص الوعد، ستجد أن لديه حالة fulfilled
، و value
مضبوطة على القيمة التي قمت بتمريرها إلى resolve
:
Output__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: "We did it!"
كما هو مذكور في بداية هذا القسم، الوعد هو كائن قد يعيد قيمة. بعد أن يتم استيفاءه بنجاح، تتغير قيمة value
من undefined
إلى أن تكون معبأة بالبيانات.
A promise can have three possible states: pending, fulfilled, and rejected.
- معلق – الحالة الابتدائية قبل الاستيفاء أو الرفض
- تم الوفاء – عملية ناجحة، تم حل الوعد
- تم رفضه – فشلت العملية، تم رفض الوعد
بعد أن يتم تحقيقه أو رفضه، يُعتبر الوعد قد استوفي.
الآن بعد أن لديك فكرة عن كيفية إنشاء الوعود، دعنا نلقي نظرة على كيفية قد يستهلك المطورون هذه الوعود.
استهلاك الوعد
الوعد الذي تم ذكره في القسم السابق قد تم استيفاؤه بقيمة، ولكنك أيضًا ترغب في القدرة على الوصول إلى القيمة. الوعود تحتوي على طريقة تسمى then
التي ستعمل بعد أن يصل الوعد إلى الحل
في الكود. ستقوم then
بإرجاع قيمة الوعد كمعلمة.
هكذا يمكنك إعادة القيمة وتسجيلها للوعد المثال:
الوعد الذي قمت بإنشائه كان لديه [[PromiseValue]]
قيمته لقد فعلنا ذلك!
. هذه القيمة هي ما سيتم تمريره إلى الدالة المجهولة باسم response
:
OutputWe did it!
حتى الآن، لم يشمل المثال الذي أنشأته أي طلب واجهة برمجة تطبيقات ويب غير متزامنة—فقط شرح كيفية إنشاء واستيفاء واستهلاك وعد JavaScript أصلي. باستخدام setTimeout
، يمكنك اختبار طلب غير متزامن.
الكود التالي يحاكي البيانات المرجعية التي تم إرجاعها من طلب غير متزامن كوعد:
استخدام بنية then
يضمن أن الاستجابة response
ستتم تسجيلها فقط عند اكتمال عملية setTimeout
بعد 2000
ميلي ثانية. كل هذا يتم بدون تضمين الاستدعاءات الارتجالية.
الآن بعد مرور ثانيتين، سيتم حل قيمة الوعد وسيتم تسجيلها في then
:
OutputResolving an asynchronous request!
يمكن أيضًا ربط الوعود لتمرير البيانات إلى أكثر من عملية غير متزامنة واحدة. إذا تم إرجاع قيمة في then
، يمكن إضافة then
آخر سيكتمل بقيمة الإرجاع من then
السابق:
سيقوم الاستجابة المكتملة في الthen الثاني بتسجيل القيمة المُرجعة:
OutputResolving an asynchronous request! And chaining!
نظرًا لأنه يمكن ربط الthen، فإنه يتيح جعل استهلاك الوعود يبدو أكثر تزامنًا من الاستدعاءات الارتجالية، حيث لا يحتاجون إلى تضمين. وهذا سيسمح بكتابة شفرة قابلة للقراءة أكثر يمكن صيانتها والتحقق منها بسهولة.
معالجة الأخطاء
حتى الآن، لقد تعاملت فقط مع وعد يتم حله بنجاح resolve
، مما يضع الوعد في حالة fulfilled
. ولكن في كثير من الأحيان مع طلب غير متزامن يجب أيضًا التعامل مع خطأ – إذا كانت واجهة برمجة التطبيقات API لا تعمل، أو إذا تم إرسال طلب غير صالح أو غير مصرح به. يجب أن يكون بإمكان الوعد التعامل مع كلا الحالتين. في هذا القسم، ستقوم بإنشاء وظيفة لاختبار كل من حالة النجاح والخطأ في إنشاء واستهلاك الوعد.
ستقوم هذه الوظيفة getUsers
بتمرير علامة إلى وعد، وستعيد الوعد:
قم بإعداد الكود بحيث إذا كانت قيمة onSuccess
تساوي true
، فإن المهلة ستكتمل ببعض البيانات. إذا كانت قيمة false
، فإن الوظيفة سترفض بخطأ:
بالنسبة للنتيجة الناجحة، يتم إرجاع كائنات JavaScript تمثل بيانات مستخدمين عينية.
للتعامل مع الخطأ، ستستخدم الطريقة الفرعية catch
. وسيتيح لك ذلك استدعاءً فاشلاً مع الـ error
كمعلمة.
قم بتشغيل أمر getUser
مع تعيين onSuccess
إلى false
، باستخدام الطريقة then
لحالة النجاح والطريقة catch
للخطأ:
منذ تم تشغيل الخطأ، سيتم تخطي then
وسيتعامل catch
مع الخطأ:
OutputFailed to fetch data!
إذا قمت بتبديل العلم واستبداله بـ resolve
، فسيتم تجاهل catch
، وسيتم إرجاع البيانات بدلاً من ذلك:
سيؤدي هذا إلى الحصول على بيانات المستخدم:
Output(3) [{…}, {…}, {…}]
0: {id: 1, name: "Jerry"}
1: {id: 2, name: "Elaine"}
3: {id: 3, name: "George"}
للإشارة، إليك جدول يحتوي على أساليب المعالجة على كائنات Promise
:
Method | Description |
---|---|
then() |
Handles a resolve . Returns a promise, and calls onFulfilled function asynchronously |
catch() |
Handles a reject . Returns a promise, and calls onRejected function asynchronously |
finally() |
Called when a promise is settled. Returns a promise, and calls onFinally function asynchronously |
قد تكون الوعود مربكة، سواء بالنسبة للمطورين الجدد أو للمبرمجين الذين لم يعملوا في بيئة غير متزامنة من قبل. ومع ذلك، كما ذكر، فمن الأكثر شيوعًا استهلاك الوعود بدلاً من إنشائها. عادةً، سيوفر واجهة برمجة تطبيقات الويب للمتصفح أو مكتبة الطرف الثالث الوعد، وستحتاج فقط إلى استهلاكه.
في القسم الأخير من الوعد، سيقوم هذا البرنامج التعليمي بذكر حالة استخدام شائعة لواجهة برمجة تطبيقات الويب التي تعيد الوعود: واجهة استرجاع البيانات (Fetch API).
استخدام واجهة استرجاع البيانات (Fetch API) مع الوعود
واحدة من أكثر واجهات تطبيقات الويب المفيدة والمستخدمة بشكل متكرر التي ترجع وعدا هي واجهة Fetch API، التي تتيح لك إجراء طلب للموارد بشكل غير متزامن عبر الشبكة. fetch
هي عملية مكونة من جزأين، وبالتالي تتطلب ربطًا لاحقًا باستخدام then
. يوضح هذا المثال كيفية استخدام واجهة GitHub API لاسترداد بيانات المستخدم، مع التعامل مع أي خطأ محتمل أيضًا:
تُرسل طلب fetch
إلى عنوان URL https://api.github.com/users/octocat
، والذي ينتظر بشكل غير متزامن استجابة. يمر الـ then
الأول بالاستجابة إلى دالة مجهولة تقوم بتنسيق الاستجابة كـ بيانات JSON، ثم يمر بيانات JSON إلى then
الثاني الذي يقوم بتسجيل البيانات في وحدة التحكم. تُسجل أي خطأ في الـ catch
إلى وحدة التحكم أيضًا.
تشغيل هذا الكود سينتج عنه ما يلي:
Outputlogin: "octocat",
id: 583231,
avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4"
blog: "https://github.blog"
company: "@github"
followers: 3203
...
هذه هي البيانات المطلوبة من https://api.github.com/users/octocat
، المعروضة بتنسيق JSON.
أظهرت هذه الجزء من الدورة التعليمية أن الوعود تدمج العديد من التحسينات للتعامل مع الشفرة غير المتزامنة. ومع ذلك، على الرغم من أن استخدام then
للتعامل مع الإجراءات غير المتزامنة أسهل لمتابعته من الهرم من الاستدعاءات الراجعة، إلا أن بعض المطورين لا يزالون يفضلون تنسيقًا متزامنًا لكتابة الشفرة غير المتزامنة. لمعالجة هذه الحاجة، قدمت ECMAScript 2016 (ES7) وظائف async
وكلمة المفتاح await
لتسهيل العمل مع الوعود.
الوظائف الغير متزامنة باستخدام async/await
الوظيفة async
تسمح لك بمعالجة الكود غير المتزامن بطريقة تبدو متزامنة. الوظائف async
لا تزال تستخدم الوعود تحت الغطاء، ولكن لها بناء جافا سكريبت التقليدي. في هذا القسم، ستقوم بتجربة أمثلة لهذا النحو البنائي.
يمكنك إنشاء وظيفة async
بإضافة الكلمة المفتاحية async
قبل الوظيفة:
على الرغم من أن هذه الوظيفة لا تعالج شيئًا غير متزامن حتى الآن، إلا أنها تتصرف بشكل مختلف عن الوظيفة التقليدية. إذا قمت بتنفيذ الوظيفة، ستجد أنها تعيد وعدًا بقيمة [[PromiseStatus]]
و [[PromiseValue]]
بدلاً من قيمة إرجاع.
جرب هذا عن طريق تسجيل استدعاء للوظيفة getUser
:
سيعطيك ذلك ما يلي:
Output__proto__: Promise
[[PromiseStatus]]: "fulfilled"
[[PromiseValue]]: Object
هذا يعني أنه يمكنك معالجة وظيفة async
باستخدام then
بنفس الطريقة التي يمكنك بها معالجة وعد. جرب هذا باستخدام الكود التالي:
يقوم هذا الاستدعاء لـ getUser
بتمرير قيمة الإرجاع إلى وظيفة مجهولة تقوم بتسجيل القيمة في وحدة التحكم.
ستتلقى ما يلي عند تشغيل هذا البرنامج:
Output{}
يمكن للدالة async
التعامل مع وعد يتم استدعاؤه داخلها باستخدام عامل await
. يمكن استخدام await
داخل دالة async
وسينتظر حتى يتم حل الوعد قبل تنفيذ الكود المحدد.
بناءً على هذه المعرفة، يمكنك إعادة كتابة طلب الاحضار من القسم الأخير باستخدام async
/await
على النحو التالي:
يضمن عوامل await
هنا أن البيانات لا تُسجَّل قبل أن يقوم الطلب بملأها بالبيانات.
الآن يمكن التعامل مع البيانات النهائية داخل الدالة getUser
، دون الحاجة إلى استخدام then
. هذه هي نتيجة تسجيل data
:
Outputlogin: "octocat",
id: 583231,
avatar_url: "https://avatars3.githubusercontent.com/u/583231?v=4"
blog: "https://github.blog"
company: "@github"
followers: 3203
...
ملاحظة: في العديد من البيئات، async
ضروري لاستخدام await
—ومع ذلك، تسمح بعض الإصدارات الجديدة من المتصفحات ونود باستخدام await
على المستوى العلوي، مما يسمح لك بتجاوز إنشاء دالة async لتغليف await
فيها.
أخيرًا، نظرًا لأنك تعاملت بالوعد المحقق داخل الدالة الغير متزامنة، يمكنك أيضًا التعامل مع الخطأ داخل الدالة. بدلاً من استخدام طريقة catch
مع then
، ستستخدم نمط try
/catch
للتعامل مع الاستثناء.
أضف الكود المظلل التالي:
البرنامج سينتقل الآن إلى كتلة catch
إذا حدث خطأ وسيسجل هذا الخطأ في وحدة التحكم.
يتم التعامل في الوقت الحالي مع الكود الجافا سكريبت الغير متزامن بشكل رئيسي باستخدام جملة async
/await
، ولكن من المهم أن يكون لديك معرفة عملية بكيفية عمل الوعود، خاصة أن الوعود قادرة على ميزات إضافية لا يمكن التعامل معها باستخدام async
/await
، مثل دمج الوعود مع Promise.all()
.
ملاحظة: يمكن إعادة إنتاج جملة async
/await
باستخدام مولدات مجتمعة مع الوعود لإضافة المزيد من المرونة إلى كودك. لمعرفة المزيد، تفضل بزيارة الدرس الخاص بنا حول فهم المولدات في جافا سكريبت.
الختام
بسبب أن واجهات برمجة تطبيقات الويب غالبًا ما توفر البيانات بشكل غير متزامن، فإن تعلم كيفية التعامل مع نتيجة الإجراءات غير المتزامنة هو جزء أساسي من كونك مطور JavaScript. في هذه المقالة، تعلمت كيفية استخدام بيئة الاستضافة للتعامل مع ترتيب تنفيذ الشفرة باستخدام الـ المكدس وال قائمة الانتظار. كما جربت أمثلة على ثلاث طرق للتعامل مع نجاح أو فشل حدث غير متزامن، باستخدام إرجاع الاستدعاءات، الوعود، وجملة async
/await
. وأخيرًا، استخدمت واجهة برمجة الويب Fetch للتعامل مع الإجراءات غير المتزامنة.
لمزيد من المعلومات حول كيفية تعامل المتصفح مع الأحداث المتوازية، اقرأ نموذج التوازي وحلقة الأحداث على شبكة مطوري Mozilla. إذا كنت ترغب في معرفة المزيد عن JavaScript، عد إلى سلسلةنا كيفية البرمجة بلغة JavaScript.