التكرار اللامتزامن والمولدات
يسمح لنا التكرار اللامتزامن بالتكرار على البيانات التي تأتي بشكل لامتزامن، حسب الطلب. مثل، على سبيل المثال، عندما نقوم بتنزيل شيء تدريجيًا عبر الشبكة. وتجعل المولدات اللامتزامنة ذلك أكثر ملاءمة.
المتكررات الغير متزامنة Async iterators
تذكر المتكررات
دعنا نستدعي الموضوع حول المتكررات.
الفكرة هي أن لدينا كائنًا، مثل range هنا:
let range = {
from: 1,
to: 5,
};
… ونريد استخدام حلقة for..of عليه، مثل for(value of range)، للحصول على القيم من 1 إلى 5.
بعبارة أخرى، نريد إضافة “قابلية التكرار” إلى الكائن.
يمكن تنفيذ ذلك باستخدام طريقة خاصة بالاسم Symbol.iterator:
- يتم استدعاء هذه الطريقة من قبل بناء
for..ofعند بدء الحلقة، ويجب أن تعيد كائنًا يحتوي على الطريقةnext. - في كل تكرار، ييتم استدعاء طريقة
next()للحصول على القيمة التالية. - يجب أن ترجع الطريقة
next()قيمة في الشكل{done: true/false, value:<loop value>}، حيث يعنيdone:trueنهاية الحلقة.
هنا تنفيذ للمتكرر range:
let range = {
from: 1,
to: 5,
[Symbol.iterator]() { // يستدعى مرة واحدة، في بداية for..of
return {
current: this.from,
last: this.to,
next() { // يستدعى في كل تكرار، للحصول على القيمة التالية
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
for(let value of range) {
alert(value); // 1 ثم 2، ثم 3، ثم 4،ثم 5
}
إذا كانت هناك أي عدم وضوح، يرجى زيارة الفصل Iterables، حيث يوفر كافة التفاصيل حول المتكررات العادية.
المتكررات الأسنكرونية
التكرار الأسنكروني يُحتاج عندما تأتي القيم بشكل أسنكروني: بعد setTimeout أو نوع آخر من التأخير.
الحالة الأكثر شيوعًا هي أن الكائن يحتاج إلى إجراء طلب شبكة لتسليم القيمة التالية، وسنرى مثالًا حيًا عليه لاحقًا.
لجعل كائن قابلًا للتكرار بشكل أسنكروني:
- استخدم
Symbol.asyncIteratorبدلاً منSymbol.iterator. - يجب أن ترجع التابع
next()وعدًا (ليتم الوفاء به بالقيمة التالية).- يتعامل الكلمة
asyncمع ذلك، لذلك يمكننا ببساطة جعلnext()مُشار إليها بـasync.
- يتعامل الكلمة
- لتكرار مثل هذا الكائن، يجب علينا استخدام حلقة
for await (let item of iterable)، حيث يتم استخدام كلمةawait.
لنبدأ بمثال لجعل كائن range قابلًا للتكرار بشكل أسنكروني، وشبيه بالذي سبق، ولكنه الآن سيعيد القيم بشكل أسنكروني، واحدة في الثانية.
كل ما نحتاج إلى فعله هو إجراء بعض الاستبدالات في الشفرة أعلاه:
let range = {
from: 1,
to: 5,
[Symbol.asyncIterator]() { // (1)
return {
current: this.from,
last: this.to,
async next() { // (2)
// ملاحظة: يمكننا استخدام "await" داخل next ويتم التعامل معها باستخدام async:
await new Promise(resolve => setTimeout(resolve, 1000)); // (3)
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
for await (let value of range) { // (4)
alert(value); // 1,2,3,4,5
}
})()
كما نرى فإن الشكل والتركيب مثل الـiterators العادية:
- لجعل الكائن iterable بشكل غير متزامن، يجب أن يحتوى على الدالة
Symbol.asyncIterator(1). - هذه الدالة يجب أن تقوم بإرجاع الكائن باستخدام
next()والتى تقوم بإرجاع promise(2). - لا يجب أن تكون الدالة
next()عباره عنasyncويمكن أن تكون دالة عادية تُرجع promise ولكن الكلمةasyncتمكننا من استخدام الكلمةawaitوهذا مناسب. وهنا يمكننا التأخير ثانية(3). - للقيام بالتكرار نستخدم التكرار
for await(let value of range)(4)ونضع الكلمة “await” بعد “for”. وهي تستدعي الدالةrange[Symbol.asyncIterator]()مرة واحدة وثم الدالةnext()من أجل القيم.
هذا جدول صغير يوضح الفروقات:
| Iterators | Async iterators | |
|---|---|---|
| الدوال لإنشاء متكرر | Symbol.iterator |
Symbol.asyncIterator |
القيمة التى تُرجعها next() |
أى فيمة | Promise |
| للتكرار، نستخدم | for..of |
for await..of |
... لا تعمل بشكل غير متزامنالأشياء التي تتطلب iterators عادية ومتزامنة synchronous لا تعمل فى المناطق الغير متزامنة.
على سبيل المثال، لا تعمل الـspread syntax:
alert([...range]); // خطأ، لا يوجد Symbol.iterator
هذا طبيعي، حيث يتوقع العثور على Symbol.iterator وليس Symbol.asyncIterator.
هذا أيضًا الحال بالنسبة لـ for..of: الصيغة بدون await تحتاج إلى Symbol.iterator.
إستدعاء المولدات
دعونا نستدعي المولدات، حيث تسمح بجعل كود التكرار أقصر بكثير. في معظم الأحيان، عندما نريد جعل شيء مكرر، سنستخدم المولدات.
للبساطة، بدون بعض الأشياء المهمة، فإنها “دوال تنتج (تصدر) قيمًا”. لقد تم شرحها بالتفصيل في الفصل المُنشِئات Generators.
تتم وسم المولدات باستخدام function* (لاحظ النجمة) واستخدام yield لإصدار قيمة، ثم يمكننا استخدام for..of للحلقة عليها.
يقوم هذا المثال بتوليد سلسلة من القيم من start إلى end:
function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
yield i;
}
}
for(let value of generateSequence(1, 5)) {
alert(value); // 1، ثم 2، ثم 3، ثم 4، ثم 5
}
كما نعلم بالفعل، لجعل كائن مكرر، يجب أن نضيف Symbol.iterator إليه.
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
return <object with next to make range iterable>
}
}
الممارسة الشائعة لـ Symbol.iterator هي إرجاع مولد، حيث يجعل الكود أقصر، كما يمكنك ملاحظته:
let range = {
from: 1,
to: 5,
*[Symbol.iterator]() { // اختصار لـ [Symbol.iterator]: function*()
for(let value = this.from; value <= this.to; value++) {
yield value;
}
}
};
for(let value of range) {
alert(value); // 1، ثم 2، ثم 3، ثم 4، ثم 5
}
يرجى الرجوع إلى الفصل المُنشِئات Generators إذا كنت ترغب في المزيد من التفاصيل.
في المولدات العادية، لا يمكننا استخدام await. يجب أن تأتي كافة القيم بنفس المزامنة كما هو مطلوب من بناء الحلقة for..of.
ماذا لو كنا نريد إنشاء قيم بشكل غير متزامن؟ من طلبات الشبكة، على سبيل المثال.
دعونا ننتقل إلى المولدات الغير متزامنة لجعل ذلك ممكناً.
المولدات الغير متزامنة (أخيراً)
لمعظم التطبيقات العملية، عندمانريد جعل كائن يولد سلسلة من القيم بشكل غير متزامن، يمكننا استخدام مولد غير متزامن.
الصيغة بسيطة: نقوم بإضافة async قبل function* وذلك يجعل المولد غير متزامن.
ثم نستخدم for await (...) للتكرار عليه، كما يلي: