التزامن في TypeScript: الخيوط، الجوروتينات و async/await — نفهمها عمليًا

مدرب بايثون أونلاين للمبتدئين

تعلم Python بسهولة دون إرهاق نظري. حل مهام عملية مع التحقق التلقائي واكتب الكود مباشرة في المتصفح.

ابدأ الدورة

التزامن في TypeScript: من Event Loop إلى Web Workers

\n\n

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

\n\n

في هذه المقالة، لن نكتفي بسرد المصطلحات. سنفهم كيف يعمل async/await بالفعل، وما يختبئ تحت غطاء Event Loop، وهل يمكن استخدام "الجوروتينات" في TypeScript كما في Go، ومتى يجب استخدام الخيوط الحقيقية عبر Web Workers. هيا بنا!

\n\n

1. عدم التزامن مقابل التوازي: ما الفرق؟

\n\n

قبل أن نتعمق في الكود، دعونا نتفق على المصطلحات. كثيرون يخلطون بين هذه المفاهيم، وهذا خطأ.

\n\n
    \n
  • عدم التزامن (التزامن) — هو قدرة البرنامج على بدء مهمة، والتبديل إلى مهمة أخرى بينما الأولى تنتظر (مثل انتظار رد من الخادم)، ثم العودة إليها لاحقًا. في لحظة زمنية واحدة، يتم تنفيذ تعليمة واحدة فقط.
  • \n
  • التوازي — هو تنفيذ مهمتين أو أكثر في نفس اللحظة الزمنية حرفيًا على أنوية معالج مختلفة.
  • \n
\n\n

TypeScript يوفر لنا عدم التزامن بشكل افتراضي. أما التوازي فيتطلب إنشاء خيوط منفصلة — في المتصفح هي Web Workers، وفي Node.js — worker_threads. تذكر هذا الفرق، وإلا قد تكتب كودًا "يتباطأ" بسبب حظر الخيط الرئيسي.

\n\n

2. Event Loop والكولباك — أساس التزامن في TypeScript

\n\n

السر الرئيسي لعدم التزامن في JavaScript/TypeScript هو Event Loop (حلقة الأحداث). تخيل أن لديك قائمة مهام. الخيط الرئيسي يأخذ مهمة، ينفذها، إذا كانت متزامنة — يعطي النتيجة فورًا. أما إذا كانت عملية غير متزامنة (مثل setTimeout أو fetch)، فتُرسل "جانبًا" (إلى Web API أو libuv)، بينما يستمر الخيط الرئيسي في تنفيذ الكود التالي.

\n\n

عندما تكتمل العملية غير المتزامنة، يدخل الكولباك الخاص بها إلى قائمة الانتظار (Task Queue أو Microtask Queue). Event Loop يتحقق باستمرار مما إذا كان مكدس الاستدعاءات فارغًا، وإذا كان فارغًا — يأخذ المهمة الأولى من قائمة الانتظار وينفذها.

\n\n
// مثال بسيط يوضح ذلك\nconsole.log('1. البداية');\n\nsetTimeout(() => {\n    console.log('3. المؤقت عمل');\n}, 0);\n\nPromise.resolve().then(() => {\n    console.log('2. الميكروتاسك منفذة');\n});\n\nconsole.log('4. النهاية');\n\n// الإخراج: 1, 4, 2, 3
\n\n

لاحظ: Promise (ميكروتاسك) نُفذ قبل setTimeout (ماكروتاسك)، رغم أن تأخير المؤقت هو 0 مللي ثانية. هذه معرفة مهمة لـ تحسين TypeScript.

\n\n

3. Async/Await — سكر يغير كل شيء

\n\n

مع ظهور ES2017، انقسم عالم JS إلى "قبل" و"بعد". Async/await هو سكر نحوي فوق الـ Promises، مما يجعل الكود غير المتزامن يبدو وكأنه متزامن. لا يضيف خيوطًا، لكنه يحسن قابلية القراءة بشكل جذري.

\n\n
// دالة تحاكي التأخير\nfunction delay(ms: number): Promise<void> {\n    return new Promise(resolve => setTimeout(resolve, ms));\n}\n\n// دالة غير متزامنة باستخدام async/await\nasync function fetchUserData(userId: number): Promise<string> {\n    console.log(`أطلب بيانات المستخدم ${userId}...`);\n    await delay(2000); // محاكاة طلب شبكة\n    return `بيانات المستخدم ${userId}`;\n}\n\nasync function main() {\n    console.log('البداية');\n    const user1 = await fetchUserData(1);\n    console.log(                

المدونات

توصيات الكتب