تُعدّ async/await تقنية برمجة حديثة تمكّن المطورين من التعامل مع العمليات غير المتزامنة بطريقة أكثر وضوحًا وتنظيمًا. وتفيد هذه التقنية بشكل خاص في الحالات التي تحتاج فيها التطبيقات إلى أداء عدة مهام بشكل متزامن، مثل جلب البيانات من خادم، دون حجب الخيط الرئيسي. وقد أصبحت async/await جزءًا لا يتجزأ من لغات برمجة كثيرة، منها C# و Python و Swift و Kotlin (في صورة دوال التعليق) و JavaScript، حيث تبسّط العمل مع الوعود وتجعل الشيفرة أكثر بداهة وأسهل في الصيانة.
تعمل async/await بالسماح للدالة بانتظار عملية غير متزامنة، تُرجع في JavaScript وعدًا، قبل مواصلة بقية شيفرتها. وهذا يعطي انطباعًا بأن الشيفرة متزامنة، مع أن العمليات تجري فعليًا بشكل غير متزامن. ولا تحسّن هذه التقنية قابلية القراءة فحسب، بل تقلل أيضًا خطر الأخطاء، ما يجعلها أداة محورية لمطوري التطبيقات المعاصرين الذين يريدون إنشاء تطبيقات فعالة ومستجيبة.
تُعدّ async/await تقنية برمجة حديثة تبسّط التعامل مع العمليات غير المتزامنة في JavaScript بجعل الشيفرة أكثر وضوحًا وتنظيمًا. فالكلمة المفتاحية async تعلن أن الدالة غير متزامنة، ما يسمح باستخدام await داخلها. وتوقف await تنفيذ الدالة مؤقتًا حتى يُحسم وعد معين، ما يتيح كتابة شيفرة غير متزامنة تبدو كأنها شيفرة متزامنة. وهذا يحسّن معالجة الأخطاء وقابلية الصيانة معًا، إذ يقلل الحاجة إلى دوال الاستدعاء المتداخلة وسلاسل الوعود المعقدة. وتفيد async/await بشكل خاص في التطبيقات التي تحتاج إلى التعامل مع عدة مهام غير متزامنة متزامنة، مثل الطلبات الشبكية وعمليات الملفات، ما ينتج عنه تجربة مستخدم أكثر كفاءة واستجابة.
More about Async/Await
تُعدّ async/await تقنية برمجة متقدمة تتيح التعامل البسيط والفعال مع العمليات غير المتزامنة في تطوير البرمجيات الحديث. إذ تمكّن هذه التقنية المطورين من كتابة شيفرة تبدو متزامنة لكنها تُنفَّذ فعليًا بشكل غير متزامن، ما يحسّن قابلية قراءة الشيفرة وصيانتها معًا. وتُعدّ async/await مناسبة بشكل خاص للتطبيقات التي يكون فيها الأداء والاستجابة أمرين حاسمين، مثل تطبيقات الويب والأجهزة المحمولة التي تتطلب استرجاعًا سريعًا للبيانات أو طلبات شبكية أو عمليات على الملفات.
من الناحية التقنية، تُعدّ async/await ميزة نحوية في كثير من لغات البرمجة الحديثة، مثل JavaScript و Python، تقوم على الوعود أو العقود المستقبلية أو كائنات مشابهة. فعندما تُوسم دالة بـ «async»، يمكن أن تحتوي على تعبيرات «await» توقف تنفيذ الدالة مؤقتًا حتى تكتمل العملية غير المتزامنة. وهذا يمكّن المطورين من كتابة شيفرة تبدو خطية لكنها لا تحجب الخيط الرئيسي، ما يجعل التطبيقات أكثر استجابة وكفاءة.
يمكن استخدام async/await في ثلاثة سيناريوهات مختلفة، يدعم كل منها أنواعًا مختلفة من العمليات غير المتزامنة:
استرجاع البيانات: تُستخدم async/await لجلب البيانات من مصادر خارجية، مثل واجهات برمجة التطبيقات أو قواعد البيانات. فعلى سبيل المثال، يستطيع تطبيق ويب استخدام «await» لإيقاف دالة مؤقتًا أثناء انتظار استجابة من خادم، ما يتيح معالجة البيانات فور توفرها دون تجميد واجهة المستخدم.
عمليات الملفات: يمكن أيضًا استخدام async/await لقراءة الملفات أو كتابتها بشكل غير متزامن. وهذا مفيد في التطبيقات التي تحتاج إلى التعامل مع كميات كبيرة من البيانات، إذ يمنع التطبيق من التوقف عن الاستجابة أثناء تنفيذ عمليات الملفات.
الطلبات الشبكية: تتيح async/await التعامل مع الطلبات الشبكية بكفاءة بانتظار الاستجابات دون حجب الخيط الرئيسي للتطبيق. وهذا مهم بشكل خاص في التطبيقات التي تتطلب اتصالًا في الوقت الفعلي أو تحديثات متكررة من خادم.
ومن أكبر نقاط قوة async/await قدرتها على تبسيط التعامل مع العمليات غير المتزامنة. فباستخدام «await»، يستطيع المطورون كتابة شيفرة يسهل فهمها وصيانتها، إذ تشبه الشيفرة المتزامنة مع أنها تُنفَّذ بشكل غير متزامن. وهذا يقلل خطر الأخطاء ويسهّل تصحيح التطبيقات المعقدة.
وتُعدّ كفاءة async/await ميزة مهمة أخرى. فبتجنب حجب الخيط الرئيسي، تستطيع التطبيقات البقاء مستجيبة حتى أثناء تنفيذ عمليات غير متزامنة طويلة. وهذا يحسّن تجربة المستخدم بشكل كبير، إذ تستطيع التطبيقات الاستجابة بسرعة لإدخال المستخدم أثناء التعامل مع المهام الخلفية. غير أن الحسابات الثقيلة على المعالج لا تزال تحجب الخيط، ويجب نقلها إلى خيط منفصل أو عامل منفصل.
عمليًا، غيّرت async/await طريقة كتابة الشيفرة غير المتزامنة في تطوير البرمجيات الحديث. وتُستخدم في تطبيقات الويب والأجهزة المحمولة للتعامل مع كل شيء من استرجاع البيانات إلى الاتصال الشبكي، ما يتيح بناء تطبيقات فعالة وسهلة الاستخدام في آن واحد. كما تسهّل async/await دمج الميزات والتقنيات الجديدة، إذ تبسّط التعامل مع العمليات غير المتزامنة المعقدة.
خلاصة القول، تُعدّ async/await تقنية تجمع بين البساطة والكفاءة وقابلية القراءة في حل يجعل البرمجة غير المتزامنة أكثر بداهة وإتاحة للمطورين. وتجعلها قدرتها على تبسيط العمليات غير المتزامنة المعقدة أداة مهمة في تطوير البرمجيات الحديث، حيث تُعدّ التطبيقات السريعة والمستجيبة مطلوبة.
Frequently asked questions about Async/Await
ما الفرق بين async/await ودوال الاستدعاء التقليدية؟
تُعدّ async/await صيغة حديثة في JavaScript، قُدمت في ES2017، تسهّل العمل مع الشيفرة غير المتزامنة بكتابتها بأسلوب أقرب إلى المتزامن. إذ تتيح async/await كتابة شيفرة غير متزامنة أسهل في القراءة والفهم، لأنها تشبه الشيفرة المتزامنة، ولكن دون حجب الخيط الرئيسي. فعندما تُوسم دالة بـ async، يمكن استخدام await داخلها لانتظار حسم وعد قبل أن تواصل الشيفرة.
أما دوال الاستدعاء التقليدية، فتتطلب تمرير دالة كوسيط، تُستدعى عندما تنتهي عملية غير متزامنة. وقد يؤدي ذلك إلى ما يُسمى غالبًا «callback hell»، حيث تجعل دوال الاستدعاء المتداخلة الكثيرة الشيفرة صعبة القراءة والصيانة. كما قد تجعل دوال الاستدعاء معالجة الأخطاء أكثر صعوبة، إذ يجب التقاط الأخطاء ومعالجتها في كل دالة استدعاء على حدة.
ومقارنة بدوال الاستدعاء، التي قد تؤدي إلى بنى معقدة ومتداخلة، توفر async/await طريقة أكثر خطية ووضوحًا للتعامل مع الشيفرة غير المتزامنة. وهذا يسهّل كتابة الشيفرة وصيانتها، خصوصًا في المشاريع الأكبر حيث تنتشر الشيفرة غير المتزامنة.
كيف تحسّن async/await قابلية قراءة الشيفرة غير المتزامنة؟
تحسّن async/await قابلية قراءة الشيفرة غير المتزامنة بجعلها أكثر خطية وبداهة، ما يسهّل على المطورين فهم الشيفرة وصيانتها. فباستخدام async/await، يمكن كتابة الشيفرة غير المتزامنة بطريقة تشبه الشيفرة المتزامنة، ما يقلل التعقيد بتجنب دوال الاستدعاء المتداخلة و«callback hell».
وفضلًا عن ذلك، تتيح async/await معالجة الأخطاء بفعالية أكبر باستخدام كتل try/catch المألوفة من البرمجة المتزامنة. وهذا يعني أن المطورين يستطيعون كتابة شيفرة أكثر متانة وأسهل في التصحيح والصيانة، إذ يمكن التقاط الأخطاء ومعالجتها بطريقة مركزية.
ومقارنة باستخدام الوعود مباشرة، الذي قد يتطلب التسلسل بعدة توابع .then() و .catch()، تجعل async/await الشيفرة أوضح وأقل عرضة للأخطاء، خصوصًا في التطبيقات المعقدة التي تضم عمليات غير متزامنة كثيرة يلزم تنسيقها.
هل يمكن استخدام async/await مع جميع أنواع العمليات غير المتزامنة؟
تُعدّ async/await ميزة قوية في لغات البرمجة الحديثة تتيح التعامل مع العمليات غير المتزامنة بطريقة أكثر وضوحًا وتنظيمًا. غير أنه من المهم إدراك أن async/await لا يمكن استخدامها مباشرة مع كل نوع من العمليات غير المتزامنة. ففي JavaScript، صُممت await للعمل مع الوعود، وهي الآلية المدمجة للتعامل مع الشيفرة غير المتزامنة، ومع الكائنات الأخرى من نوع «thenable»، أما القيمة التي ليست وعدًا فتُرجع فورًا ببساطة.
وباستخدام async/await، يمكن كتابة شيفرة تبدو كأنها متزامنة لكنها تُنفَّذ فعليًا بشكل غير متزامن. وهذا يجعل الشيفرة أسهل في القراءة والصيانة، إذ يمكن تجنب ما يُسمى «callback hell» وسلاسل الوعود المعقدة. ولكن للاستفادة من await، يجب أن تُرجع العملية غير المتزامنة المستخدمة وعدًا.
وثمة أنماط غير متزامنة أخرى، مثل دوال الاستدعاء ومُطلقات الأحداث، لا تدعم async/await مباشرة. ولاستخدام async/await مع هذه الأنماط، يجب أولًا تحويلها إلى وعود، وهو ما يمكن فعله بدوال مساعدة مثل util.promisify في Node.js أو بإنشاء كائن وعد يدويًا.
ومقارنة بالأساليب التقليدية للتعامل مع الشيفرة غير المتزامنة، توفر async/await طريقة أكثر بداهة لكتابة الشيفرة غير المتزامنة، لكنها تتطلب أن تكون العملية غير المتزامنة الأساسية قائمة على الوعود. وهذا يجعلها خيارًا مثاليًا لتطوير JavaScript الحديث، حيث أصبحت الوعود المعيار للعمليات غير المتزامنة. وللغات الأخرى متطلبات مكافئة، مثل Task في C# أو الكائنات القابلة للانتظار في Python.
كيف تُعالج الأخطاء في شيفرة async/await؟
عندما تستخدم دالة await ويُرفض الوعد المنتظر، يُرمى الخطأ تمامًا كما في الشيفرة المتزامنة. لذا تُعالج الأخطاء في شيفرة async/await عادة بكتلة try/catch عادية: توضع الشيفرة التي قد تفشل في جزء try، ويلتقط جزء catch الخطأ حتى يستطيع التطبيق مثلًا عرض رسالة خطأ أو إعادة المحاولة. وتُنفَّذ كتلة finally الاختيارية بغض النظر عن النتيجة، وهي مفيدة للتنظيف، مثل إخفاء مؤشر التحميل.
ومن المهم تذكر أن الدالة غير المتزامنة تُرجع وعدًا دائمًا. فإذا لم يُلتقط الخطأ داخل الدالة، يُرفض الوعد المُرجع، ويجب حينئذ أن تعالج الشيفرة المستدعية الخطأ، إما بـ try/catch حول await الخاصة بها أو بـ .catch() على الاستدعاء. وتسبب الوعود المرفوضة التي لا يعالجها أحد تحذيرات في المتصفح وقد تنهي العملية في Node.js. ولاحظوا أيضًا أن fetch() لا ترفض إلا عند أخطاء الشبكة، لذا يجب التحقق من رموز أخطاء HTTP مثل 404 أو 500 يدويًا، مثلًا عبر response.ok.
وعندما تعمل عدة عمليات غير متزامنة في الوقت نفسه باستخدام Promise.all، يُرفض الوعد المجمّع فور فشل إحدى العمليات، ويمكن التقاط الخطأ بكتلة try/catch واحدة. وإذا كانت نتائج جميع العمليات مطلوبة، بما فيها تلك التي تفشل، فيمكن استخدام Promise.allSettled بدلًا من ذلك. ومقارنة بدوال الاستدعاء وسلاسل .then/.catch الطويلة، توفر async/await معالجة أخطاء أوضح، لأنه يمكن استخدام بنية try/catch نفسها للشيفرة المتزامنة وغير المتزامنة على حد سواء.
ما آثار استخدام async/await على الأداء؟
لاستخدام async/await في البرمجة آثار على الأداء أساسًا لأنها تسهّل كتابة شيفرة غير حاجبة، لا لأن الصيغة نفسها أسرع. إذ تمكّن الشيفرة غير المتزامنة التطبيق من التعامل مع عدة مهام بشكل متزامن دون حجب الخيط الرئيسي، وهو أمر مفيد بشكل خاص في التطبيقات التي تتطلب استجابة سريعة واستخدامًا فعالًا للموارد. فبينما تنتظر مهمة غير متزامنة، يكون الخيط الرئيسي حرًا لأداء مهام أخرى، ما قد يحسّن الأداء الإجمالي وتجربة المستخدم. ومقارنة باستخدام الوعود مباشرة، فإن أداء async/await هو نفسه عمليًا.
وفضلًا عن ذلك، تستطيع async/await تقليل تعقيد الشيفرة بإلغاء الحاجة إلى دوال الاستدعاء، التي كثيرًا ما تؤدي إلى ما يُسمى «callback hell». وهذا يسهّل على المطورين كتابة الشيفرة وفهمها، ما قد يؤدي إلى أزمنة تطوير أسرع وأخطاء أقل. ومن المزالق الشائعة انتظار عمليات مستقلة واحدة تلو الأخرى، فتجري تسلسليًا، بينما يكون بدؤها معًا وانتظارها باستخدام Promise.all أسرع.
ومقارنة بالأساليب التقليدية للبرمجة غير المتزامنة، التي كثيرًا ما تتطلب تعاملًا أكثر تعقيدًا مع الخيوط والموارد، توفر async/await حلًا أكثر أناقة وكفاءة. وهي مفيدة بشكل خاص في البيئات التي يكون فيها تحسين الموارد والاستجابة السريعة أمرين حرجين، مثل تطبيقات الأجهزة المحمولة والويب، حيث يمكن تحسين تجربة المستخدم بشكل كبير بتقليل أوقات الانتظار إلى أدنى حد وزيادة استجابة التطبيق إلى أقصى حد.
كيف تعمل async/await فيما يتعلق بحلقة الأحداث في JavaScript؟
تُعدّ async/await في JavaScript تحسينًا نحويًا يسهّل العمل مع الشيفرة غير المتزامنة بمحاكاة الشيفرة المتزامنة. وهي تعمل بترك حلقة الأحداث في JavaScript تتعامل مع العمليات غير المتزامنة، بحيث تستطيع الشيفرة «انتظار» حسم وعد دون حجب الخيط الرئيسي. فعندما تُستدعى دالة غير متزامنة، تُرجع وعدًا، ويمكن للشيفرة داخل الدالة استخدام الكلمة المفتاحية await لإيقاف التنفيذ مؤقتًا حتى يُوفى الوعد المرتبط أو يُرفض.
وتؤدي حلقة الأحداث دورًا محوريًا في هذه العملية: فعند الوصول إلى تعبير await، تُعلَّق الدالة غير المتزامنة ويصبح الخيط الرئيسي حرًا للتعامل مع مهام أخرى، مثل تفاعلات المستخدم أو عمليات غير متزامنة أخرى، حتى يجهز الوعد المنتظر. ثم تُستأنف بقية الدالة كمهمة دقيقة. وهذا يضمن بقاء التطبيق مستجيبًا حتى أثناء تنفيذ مهام غير متزامنة معقدة.
ومقارنة بالأساليب التقليدية القائمة على دوال الاستدعاء، التي قد تؤدي إلى «callback hell» وتجعل الشيفرة أصعب في القراءة والصيانة، توفر async/await بنية أكثر خطية وأسهل فهمًا. وهذا يسهّل على المطورين كتابة الشيفرة غير المتزامنة وتصحيحها مع الاستفادة من حلقة الأحداث في JavaScript للتعامل الفعال مع العمليات غير المتزامنة.
Async/Await for dummies
تُعدّ async/await طريقة ذكية لترك الحاسوب ينشغل بأمور أخرى بدلًا من التوقف أثناء انتظار انتهاء مهمة ما. تخيّلوا خَبز كعكة ومشاهدة فيلم في الوقت نفسه. فبدلًا من الوقوف وانتظار انتهاء خَبز الكعكة، توضع الكعكة في الفرن ويُشاهد الفيلم في الأثناء. وعندما تجهز الكعكة، يصفّر الفرن ويمكن إخراجها دون إضاعة أي وقت. وتعمل async/await بالطريقة نفسها في البرمجة. فهي تساعد البرامج على التعامل مع عدة مهام في الوقت نفسه، حتى تعمل بشكل أسرع وأكثر كفاءة، دون أن يضطر المستخدمون إلى الانتظار بلا داعٍ.