أطلق العنان لقوة مُكرِّرات JavaScript غير المتزامنة لمعالجة تدفقات البيانات بكفاءة وأناقة. تعلّم كيفية التعامل مع تدفقات البيانات غير المتزامنة بفاعلية.
مُكرِّرات JavaScript غير المتزامنة: دليل شامل لمعالجة التدفقات
في عالم تطوير JavaScript الحديث، يعد التعامل مع تدفقات البيانات غير المتزامنة مطلبًا متكررًا. سواء كنت تجلب بيانات من واجهة برمجة تطبيقات (API)، أو تعالج أحداثًا في الوقت الفعلي، أو تعمل مع مجموعات بيانات كبيرة، فإن الإدارة الفعالة للبيانات غير المتزامنة أمر بالغ الأهمية لبناء تطبيقات سريعة الاستجابة وقابلة للتطوير. توفر مُكرِّرات JavaScript غير المتزامنة حلاً قويًا وأنيقًا لمواجهة هذه التحديات.
ما هي المُكرِّرات غير المتزامنة؟
المُكرِّرات غير المتزامنة هي ميزة حديثة في JavaScript تسمح لك بالتكرار على مصادر البيانات غير المتزامنة، مثل التدفقات أو استجابات واجهات برمجة التطبيقات غير المتزامنة، بطريقة محكومة ومتسلسلة. هي تشبه المُكرِّرات العادية، ولكن مع فارق رئيسي هو أن دالة next()
الخاصة بها تُرجع Promise. هذا يسمح لك بالعمل مع البيانات التي تصل بشكل غير متزامن دون حجب الخيط الرئيسي.
فكر في المُكرِّر العادي كطريقة للحصول على العناصر من مجموعة واحدًا تلو الآخر. تطلب العنصر التالي، وتحصل عليه على الفور. أما المُكرِّر غير المتزامن، من ناحية أخرى، فهو يشبه طلب العناصر عبر الإنترنت. تقوم بتقديم الطلب (استدعاء next()
)، وبعد فترة، يصل العنصر التالي (يتم حل الـ Promise).
المفاهيم الأساسية
- المُكرِّر غير المتزامن (Async Iterator): كائن يوفر دالة
next()
تُرجع Promise يتم حله إلى كائن يحتوي على خاصيتيvalue
وdone
، بشكل مشابه للمُكرِّر العادي. تمثلvalue
العنصر التالي في التسلسل، وتشيرdone
إلى ما إذا كان التكرار قد اكتمل. - المُولِّد غير المتزامن (Async Generator): نوع خاص من الدوال يُرجع مُكرِّرًا غير متزامن. يستخدم الكلمة المفتاحية
yield
لإنتاج القيم بشكل غير متزامن. - حلقة
for await...of
: بنية لغوية مصممة خصيصًا للتكرار على المُكرِّرات غير المتزامنة. تبسط عملية استهلاك تدفقات البيانات غير المتزامنة.
إنشاء المُكرِّرات غير المتزامنة باستخدام المُولِّدات غير المتزامنة
الطريقة الأكثر شيوعًا لإنشاء المُكرِّرات غير المتزامنة هي من خلال المُولِّدات غير المتزامنة. المُولِّد غير المتزامن هو دالة تُعرَّف بصيغة async function*
. داخل الدالة، يمكنك استخدام الكلمة المفتاحية yield
لإنتاج القيم بشكل غير متزامن.
مثال: محاكاة تغذية بيانات في الوقت الفعلي
لنقم بإنشاء مُولِّد غير متزامن يحاكي تغذية بيانات في الوقت الفعلي، مثل أسعار الأسهم أو قراءات أجهزة الاستشعار. سنستخدم setTimeout
لإدخال تأخيرات مصطنعة ومحاكاة وصول البيانات غير المتزامنة.
async function* generateDataFeed(count) {
for (let i = 0; i < count; i++) {
await new Promise(resolve => setTimeout(resolve, 500)); // Simulate delay
yield { timestamp: Date.now(), value: Math.random() * 100 };
}
}
في هذا المثال:
async function* generateDataFeed(count)
تُعرِّف مُولِّدًا غير متزامن يأخذ وسيطًاcount
يشير إلى عدد نقاط البيانات التي سيتم إنشاؤها.- حلقة
for
تتكررcount
من المرات. await new Promise(resolve => setTimeout(resolve, 500))
يُدخل تأخيرًا قدره 500 مللي ثانية باستخدامsetTimeout
. هذا يحاكي الطبيعة غير المتزامنة لوصول البيانات في الوقت الفعلي.yield { timestamp: Date.now(), value: Math.random() * 100 }
تُنتج كائنًا يحتوي على طابع زمني وقيمة عشوائية. الكلمة المفتاحيةyield
توقف تنفيذ الدالة مؤقتًا وتُرجع القيمة إلى المستدعي.
استهلاك المُكرِّرات غير المتزامنة باستخدام for await...of
لاستهلاك مُكرِّر غير متزامن، يمكنك استخدام حلقة for await...of
. هذه الحلقة تتعامل تلقائيًا مع الطبيعة غير المتزامنة للمُكرِّر، حيث تنتظر حل كل Promise قبل الانتقال إلى التكرار التالي.
مثال: معالجة تغذية البيانات
لنقم باستهلاك المُكرِّر غير المتزامن generateDataFeed
باستخدام حلقة for await...of
وطباعة كل نقطة بيانات في الكونسول.
async function processDataFeed() {
for await (const data of generateDataFeed(5)) {
console.log(`Received data: ${JSON.stringify(data)}`);
}
console.log('Data feed processing complete.');
}
processDataFeed();
في هذا المثال:
async function processDataFeed()
تُعرِّف دالة غير متزامنة للتعامل مع معالجة البيانات.for await (const data of generateDataFeed(5))
تتكرر على المُكرِّر غير المتزامن الذي تُرجعهgenerateDataFeed(5)
. تضمن الكلمة المفتاحيةawait
أن الحلقة تنتظر وصول كل نقطة بيانات قبل المتابعة.console.log(`Received data: ${JSON.stringify(data)}`)
تطبع نقطة البيانات المستلمة في الكونسول.console.log('Data feed processing complete.')
تطبع رسالة تشير إلى اكتمال معالجة تغذية البيانات.
فوائد استخدام المُكرِّرات غير المتزامنة
تقدم المُكرِّرات غير المتزامنة عدة مزايا مقارنة بتقنيات البرمجة غير المتزامنة التقليدية، مثل الاستدعاءات (callbacks) والوعود (Promises):
- تحسين قابلية القراءة: توفر المُكرِّرات غير المتزامنة وحلقة
for await...of
طريقة تشبه الأسلوب المتزامن وأسهل للفهم للعمل مع تدفقات البيانات غير المتزامنة. - تبسيط معالجة الأخطاء: يمكنك استخدام كتل
try...catch
القياسية لمعالجة الأخطاء داخل حلقةfor await...of
، مما يجعل معالجة الأخطاء أكثر وضوحًا. - التعامل مع الضغط العكسي (Backpressure): يمكن استخدام المُكرِّرات غير المتزامنة لتنفيذ آليات الضغط العكسي، مما يسمح للمستهلكين بالتحكم في معدل إنتاج البيانات، ومنع استنفاد الموارد.
- قابلية التركيب: يمكن تركيب المُكرِّرات غير المتزامنة وربطها معًا بسهولة لإنشاء خطوط أنابيب بيانات معقدة.
- الإلغاء: يمكن تصميم المُكرِّرات غير المتزامنة لدعم الإلغاء، مما يسمح للمستهلكين بإيقاف عملية التكرار إذا لزم الأمر.
حالات الاستخدام في العالم الحقيقي
المُكرِّرات غير المتزامنة مناسبة تمامًا لمجموعة متنوعة من حالات الاستخدام في العالم الحقيقي، بما في ذلك:
- تدفقات واجهات برمجة التطبيقات (API Streaming): استهلاك البيانات من واجهات برمجة التطبيقات التي تدعم الاستجابات المتدفقة (مثل Server-Sent Events، WebSockets).
- معالجة الملفات: قراءة الملفات الكبيرة على شكل أجزاء (chunks) دون تحميل الملف بأكمله في الذاكرة. على سبيل المثال، معالجة ملف CSV كبير سطرًا بسطر.
- تغذية البيانات في الوقت الفعلي: معالجة تدفقات البيانات في الوقت الفعلي من مصادر مثل البورصات، منصات التواصل الاجتماعي، أو أجهزة إنترنت الأشياء (IoT).
- استعلامات قاعدة البيانات: التكرار على مجموعات نتائج كبيرة من استعلامات قاعدة البيانات بكفاءة.
- المهام الخلفية: تنفيذ المهام الخلفية طويلة الأمد التي تحتاج إلى التنفيذ على دفعات.
مثال: قراءة ملف كبير على شكل أجزاء
لنوّضح كيفية استخدام المُكرِّرات غير المتزامنة لقراءة ملف كبير على شكل أجزاء، ومعالجة كل جزء عند توفره. هذا مفيد بشكل خاص عند التعامل مع ملفات أكبر من أن تسعها الذاكرة.
const fs = require('fs');
const readline = require('readline');
async function* readLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processFile(filePath) {
for await (const line of readLines(filePath)) {
// Process each line here
console.log(`Line: ${line}`);
}
}
processFile('large_file.txt');
في هذا المثال:
- نستخدم وحدتي
fs
وreadline
لقراءة الملف سطرًا بسطر. - المُولِّد غير المتزامن
readLines
ينشئ واجهةreadline.Interface
لقراءة تدفق الملف. - حلقة
for await...of
تتكرر على أسطر الملف، وتُنتج كل سطر للمستدعي. - الدالة
processFile
تستهلك المُكرِّر غير المتزامنreadLines
وتعالج كل سطر.
هذا النهج يسمح لك بمعالجة الملفات الكبيرة دون تحميل الملف بأكمله في الذاكرة، مما يجعله أكثر كفاءة وقابلية للتطوير.
تقنيات متقدمة
التعامل مع الضغط العكسي (Backpressure)
الضغط العكسي هو آلية تسمح للمستهلكين بإبلاغ المنتجين بأنهم غير مستعدين لتلقي المزيد من البيانات. هذا يمنع المنتجين من إغراق المستهلكين والتسبب في استنفاد الموارد.
يمكن استخدام المُكرِّرات غير المتزامنة لتنفيذ الضغط العكسي عن طريق السماح للمستهلكين بالتحكم في معدل طلبهم للبيانات من المُكرِّر. يمكن للمنتج بعد ذلك تعديل معدل توليد البيانات بناءً على طلبات المستهلك.
الإلغاء
الإلغاء هو القدرة على إيقاف عملية غير متزامنة قبل اكتمالها. يمكن أن يكون هذا مفيدًا في الحالات التي لم تعد فيها العملية مطلوبة أو تستغرق وقتًا طويلاً لإكمالها.
يمكن تصميم المُكرِّرات غير المتزامنة لدعم الإلغاء من خلال توفير آلية للمستهلكين للإشارة إلى المُكرِّر بضرورة التوقف عن إنتاج البيانات. يمكن للمُكرِّر بعد ذلك تنظيف أي موارد وإنهاء عمله بأمان.
المُولِّدات غير المتزامنة مقابل البرمجة التفاعلية (RxJS)
بينما توفر المُكرِّرات غير المتزامنة طريقة قوية للتعامل مع تدفقات البيانات غير المتزامنة، فإن مكتبات البرمجة التفاعلية مثل RxJS تقدم مجموعة أكثر شمولاً من الأدوات لبناء تطبيقات تفاعلية معقدة. توفر RxJS مجموعة غنية من المعاملات (operators) لتحويل وتصفية ودمج تدفقات البيانات، بالإضافة إلى قدرات متطورة لمعالجة الأخطاء وإدارة التزامن.
ومع ذلك، توفر المُكرِّرات غير المتزامنة بديلاً أبسط وأخف وزنًا للسيناريوهات التي لا تحتاج فيها إلى القوة الكاملة لـ RxJS. كما أنها ميزة أصلية في JavaScript، مما يعني أنك لا تحتاج إلى إضافة أي تبعيات خارجية إلى مشروعك.
متى تستخدم المُكرِّرات غير المتزامنة مقابل RxJS
- استخدم المُكرِّرات غير المتزامنة عندما:
- تحتاج إلى طريقة بسيطة وخفيفة الوزن للتعامل مع تدفقات البيانات غير المتزامنة.
- لا تحتاج إلى القوة الكاملة للبرمجة التفاعلية.
- تريد تجنب إضافة تبعيات خارجية إلى مشروعك.
- تحتاج إلى العمل مع البيانات غير المتزامنة بطريقة تسلسلية ومحكومة.
- استخدم RxJS عندما:
- تحتاج إلى بناء تطبيقات تفاعلية معقدة مع تحويلات بيانات متطورة ومعالجة أخطاء.
- تحتاج إلى إدارة التزامن والعمليات غير المتزامنة بطريقة قوية وقابلة للتطوير.
- تحتاج إلى مجموعة غنية من المعاملات لمعالجة تدفقات البيانات.
- أنت على دراية بالفعل بمفاهيم البرمجة التفاعلية.
التوافق مع المتصفحات والـ Polyfills
المُكرِّرات والمُولِّدات غير المتزامنة مدعومة في جميع المتصفحات الحديثة وإصدارات Node.js. ومع ذلك، إذا كنت بحاجة إلى دعم المتصفحات أو البيئات القديمة، فقد تحتاج إلى استخدام polyfill.
تتوفر العديد من الـ polyfills للمُكرِّرات والمُولِّدات غير المتزامنة، بما في ذلك:
core-js
: مكتبة polyfill شاملة تتضمن دعمًا للمُكرِّرات والمُولِّدات غير المتزامنة.regenerator-runtime
: polyfill للمُولِّدات غير المتزامنة يعتمد على تحويل Regenerator.
لاستخدام polyfill، تحتاج عادةً إلى تضمينه في مشروعك واستيراده قبل استخدام المُكرِّرات أو المُولِّدات غير المتزامنة.
الخاتمة
توفر مُكرِّرات JavaScript غير المتزامنة حلاً قويًا وأنيقًا للتعامل مع تدفقات البيانات غير المتزامنة. إنها توفر قابلية قراءة محسنة، ومعالجة أخطاء مبسطة، والقدرة على تنفيذ آليات الضغط العكسي والإلغاء. سواء كنت تعمل مع تدفقات واجهات برمجة التطبيقات، أو معالجة الملفات، أو تغذية البيانات في الوقت الفعلي، أو استعلامات قاعدة البيانات، يمكن للمُكرِّرات غير المتزامنة أن تساعدك في بناء تطبيقات أكثر كفاءة وقابلية للتطوير.
من خلال فهم المفاهيم الأساسية للمُكرِّرات والمُولِّدات غير المتزامنة، والاستفادة من حلقة for await...of
، يمكنك إطلاق العنان لقوة معالجة التدفقات غير المتزامنة في مشاريع JavaScript الخاصة بك.
فكر في استكشاف مكتبات مثل it-tools
(https://www.npmjs.com/package/it-tools) للحصول على مجموعة من الدوال المساعدة للعمل مع المُكرِّرات غير المتزامنة.
للمزيد من الاستكشاف
- وثائق ويب MDN: for await...of
- اقتراح TC39: Async Iteration