البرمجة غير المتزامنة في Dart: async و await
تحميل صورة من الإنترنت، وقراءة ملف، وجلب بيانات من خادم: كلها عمليات تأخذ وقتاً. لو انتظر التطبيق كل عملية وهو متجمد، سيشعر المستخدم أن التطبيق معطل. الحل هو البرمجة غير المتزامنة (Asynchronous Programming): تبدأ العملية الطويلة، ويكمل التطبيق عمله، ثم تعود للنتيجة عندما تصل. وهذا من أهم الدروس التي ستحتاجها في Flutter.
المشكلة بتشبيه بسيط
Section titled “المشكلة بتشبيه بسيط”تخيّل أنك في مطعم وطلبت وجبة تحتاج 20 دقيقة:
- الطريقة المتزامنة: تقف أمام المطبخ 20 دقيقة لا تفعل شيئاً، والطابور خلفك يتوقف.
- الطريقة غير المتزامنة: تأخذ جهاز تنبيه (Buzzer)، وتجلس مع أصدقائك. عندما تجهز الوجبة، يرن الجهاز فتذهب لتستلمها.
جهاز التنبيه هذا هو بالضبط Future في Dart.
ما هو Future؟
Section titled “ما هو Future؟”Future هو وعد بقيمة ستصل لاحقاً. الدالة التي تأخذ وقتاً لا تُرجع النتيجة مباشرة، بل تُرجع Future:
Future<String> fetchUserName() { return Future.delayed(Duration(seconds: 2), () => 'سارة');}Future<String> تعني: «وعد بنص سيصل في المستقبل». واستخدمنا Future.delayed لنحاكي عملية تأخذ ثانيتين، مثل طلب من الإنترنت.
لاحظ ماذا يحدث عند استدعائها مع then:
void main() { print('1. بدأ البرنامج'); fetchUserName().then((name) => print('3. وصل الاسم: $name')); print('2. البرنامج لم يتوقف!');}1. بدأ البرنامج2. البرنامج لم يتوقف!3. وصل الاسم: سارةالسطر الثاني طُبع قبل وصول الاسم! البرنامج لم ينتظر، بل أكمل عمله، وعندما وصل الاسم بعد ثانيتين نُفذت الدالة داخل then.
async و await: كود يُقرأ بالترتيب
Section titled “async و await: كود يُقرأ بالترتيب”then تعمل، لكن مع عمليات كثيرة متتالية يصبح الكود متداخلاً ومربكاً. الحل الأجمل: async و await:
Future<void> main() async { print('جاري التحميل...'); var name = await fetchUserName(); print('أهلاً يا $name');}جاري التحميل...أهلاً يا سارةasyncبعد أقواس الدالة: «هذه الدالة فيها عمليات غير متزامنة».awaitقبل الـ Future: «انتظر هنا حتى تصل القيمة، ثم أكمل».- الدالة
asyncتُرجع دائماًFuture، لذلك كتبناFuture<void> main().
الكود الآن يُقرأ من الأعلى للأسفل كأنه عادي تماماً، لكنه لا يجمّد التطبيق: أثناء الانتظار، يستطيع التطبيق رسم الشاشة والاستجابة للمستخدم.
كتابة دالة async خاصة بك
Section titled “كتابة دالة async خاصة بك”Future<int> fetchScore() async { await Future.delayed(Duration(seconds: 1)); return 95;}
Future<void> main() async { var score = await fetchScore(); print('الدرجة: $score');}الدرجة: 95لاحظ: داخل الدالة نكتب return 95 عادياً، و Dart تغلفه تلقائياً في Future<int>.
معالجة الأخطاء
Section titled “معالجة الأخطاء”العمليات عبر الإنترنت قد تفشل: انقطع الاتصال، أو الخادم لا يرد. استخدم try و catch تماماً كالكود العادي:
Future<String> fetchCourse(bool ok) async { await Future.delayed(Duration(milliseconds: 500)); if (!ok) { throw Exception('انقطع الاتصال بالإنترنت'); } return 'Flutter';}
Future<void> main() async { try { var c = await fetchCourse(false); print(c); } catch (e) { print('حدث خطأ: $e'); } finally { print('انتهت المحاولة'); }}حدث خطأ: Exception: انقطع الاتصال بالإنترنتانتهت المحاولةthrowترمي خطأ، وcatchتلتقطه، فلا ينهار البرنامج.finallyتُنفَّذ دائماً، نجحت العملية أو فشلت. مفيدة لإخفاء مؤشر التحميل مثلاً.
عدة عمليات معاً: Future.wait
Section titled “عدة عمليات معاً: Future.wait”إذا كانت لديك عمليتان لا تعتمد إحداهما على الأخرى، فلماذا تنتظر الأولى ثم تبدأ الثانية؟ شغّلهما معاً:
var results = await Future.wait([fetchUserName(), fetchCourse(true)]);print(results);[سارة, Flutter]العملية الأولى تأخذ ثانيتين، والثانية نصف ثانية. معاً استغرقتا حوالي ثانيتين فقط بدلاً من ثانيتين ونصف، لأنهما عملتا في نفس الوقت.
مقدمة عن Stream
Section titled “مقدمة عن Stream”Future تعطيك قيمة واحدة. أما Stream فتعطيك سلسلة من القيم عبر الوقت، مثل رسائل محادثة تصل واحدة بعد الأخرى، أو موقعك على الخريطة يتحدث كل ثانية:
Stream<int> countdown(int from) async* { for (var i = from; i > 0; i--) { await Future.delayed(Duration(milliseconds: 300)); yield i; }}
Future<void> main() async { await for (var n in countdown(3)) { print(n); } print('انطلق!');}321انطلق!async*دالة تولّد Stream، وyieldترسل قيمة جديدة.await forتستقبل القيم واحدة بواحدة كلما وصلت.
إذا كان Future مثل طرد بريدي واحد، فـ Stream مثل اشتراك في مجلة تصلك أعدادها تباعاً.
جرّب بنفسك
Section titled “جرّب بنفسك”- اكتب دالة
Future<String> downloadFile(String name)تنتظر ثانية ثم تُرجع «تم تحميل …». - في
mainحمّل ثلاثة ملفات بالتتابع بـawait، واحسب الوقت بـStopwatch. - ثم حمّلها معاً بـ
Future.waitوقارن الوقت. - تحدٍّ: اجعل الدالة ترمي خطأ إذا كان اسم الملف فارغاً، والتقطه بـ
tryوcatch.
أخطاء شائعة
Section titled “أخطاء شائعة”- نسيان await: تطبع
Instance of 'Future<String>'بدلاً من القيمة. - استخدام await خارج دالة async: خطأ يرفضه المحرر.
- عدم معالجة الأخطاء: أي طلب عبر الإنترنت قد يفشل. ضع
tryوcatchحول العمليات الحساسة دائماً.
اختبر نفسك
Section titled “اختبر نفسك”سؤال
ماذا يمثل Future<int>؟
Future وعد بقيمة واحدة مستقبلية. أما سلسلة القيم المتتالية فهي Stream.
سؤال
ما فائدة Future.wait؟
Future.wait تبدأ العمليات في نفس الوقت، وتنتظر حتى تنتهي كلها، فتوفر الوقت.
الخلاصة
Section titled “الخلاصة”- العمليات الطويلة لا تجمّد التطبيق، بل تُرجع
Future: وعداً بقيمة لاحقة. asyncتعلّم الدالة، وawaitتنتظر النتيجة، والكود يُقرأ بالترتيب.tryوcatchوfinallyلمعالجة فشل العمليات.Future.waitلتشغيل عدة عمليات مستقلة معاً.Streamلسلسلة قيم عبر الوقت، وله درس كامل في آخر الكورس.