Asinxron iteratsiya va generatorlar
Asinxron iteratsiya bizga ma'lumot asinxron ravishda, ya'ni Promise lar orqali qismlar bo'lib kelganda uni birma-bir qayta ishlashga imkon beradi. Masalan, tarmoqdan sahifama-sahifa yuklanadigan ma'lumot โ bu asinxron iteratsiya uchun ideal holat.
Oddiy iteratsiyani eslaylik
Avval sinxron (oddiy) iteratsiyani eslaymiz. Iterativ obyekt yaratish uchun unga Symbol.iterator metodini qo'shamiz:
let range = {
from: 1,
to: 5,
[Symbol.iterator]() {
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) {
console.log(value); // 1, 2, 3, 4, 5
}
Bu yerda barcha qadamlar sinxron โ next() darhol tayyor {value, done} obyektini qaytaradi. Kutish yo'q. Ammo hayotda ko'p ma'lumotlar vaqt talab qilib keladi: tarmoqdan yuklab olish, fayl o'qish, ma'lumotlar bazasidan so'rov. Bunday holatlar uchun asinxron iteratsiya kerak.
Sinxron va asinxron iteratsiya farqi
Asinxron iteratsiya sinxronidan uch nuqtada farq qiladi. Farqlarni jadval kabi solishtiramiz:
- Metod nomi: sinxronda
Symbol.iterator, asinxrondaSymbol.asyncIterator; - next() qaytaruvchisi: sinxronda oddiy
{value, done}obyekt, asinxronda{value, done}ga resolve bo'ladiganPromise; - Sikl: sinxronda
for..of, asinxrondafor await..of.
next() Promise qaytargani uchun, for await..of har bir qadamda avtomatik ravishda await qiladi โ ya'ni Promise bajarilishini kutadi, so'ng keyingi qadamga o'tadi.... ishlamaydi โ u Symbol.iterator (sinxron) ni kutadi, Symbol.asyncIterator ni emas. Xuddi shunday, oddiy for..of ham asinxron iterativ bilan ishlamaydi.Symbol.asyncIterator
Obyektni asinxron iterativ qilish uchun unga Symbol.iterator emas, Symbol.asyncIterator metodini qo'shamiz. Uning next() metodi Promise qaytarishi (yoki async bo'lishi) kerak.
Quyidagi misolda range obyektini asinxron qilamiz โ har qiymat 1 sekundlik kechikish bilan keladi:
let range = {
from: 1,
to: 5,
// Symbol.iterator O'RNIGA Symbol.asyncIterator
[Symbol.asyncIterator]() {
return {
current: this.from,
last: this.to,
// next() endi async โ Promise qaytaradi
async next() {
// 1 sekund kutamiz (asinxronlikni taqlid qilamiz)
await new Promise(resolve => setTimeout(resolve, 1000));
if (this.current <= this.last) {
return { done: false, value: this.current++ };
} else {
return { done: true };
}
}
};
}
};
(async () => {
// for..of EMAS, for AWAIT..of
for await (let value of range) {
console.log(value); // 1, keyin 2, ... har biri 1 sek kechikish bilan
}
})();
E'tibor bering: next() ichida await ishlatilgani uchun u async deb belgilandi, va sikl for await..of bo'ldi. Har bir iteratsiya Promise bajarilishini kutadi.
for await..of faqat async funksiya ichida ishlatilishi mumkin (yoki top-level modul kontekstida). Oddiy sinxron funksiya ichida uni ishlata olmaysiz.Asinxron generatorlar (async function*)
Oddiy generatorlar sinxron edi โ ular ichida await ishlatib bo'lmaydi va barcha qiymatlar sinxron chiqadi. Asinxron generator esa async function* orqali yaratiladi va uning ichida await ishlatish mumkin.
async function* generateSequence(start, end) {
for (let i = start; i <= end; i++) {
// Endi await ishlata olamiz!
await new Promise(resolve => setTimeout(resolve, 1000));
yield i;
}
}
(async () => {
let generator = generateSequence(1, 5);
for await (let value of generator) {
console.log(value); // 1, 2, 3, 4, 5 โ har biri 1 sek kechikish bilan
}
})();
Sinxron va asinxron generatorlar orasidagi asosiy farqlar:
- E'lon:
function*o'rnigaasync function*; - next() qaytaruvchisi: oddiy
{value, done}emas, balki unga resolve bo'ladiganPromise; - Aylanish:
for..ofemas,for await..of; - Ichida: asinxron generatorda
awaitishlata olasiz.
next() ni qo'lda chaqirsangiz, u Promise qaytaradi. Shuning uchun uni await qilish kerak: let res = await generator.next(); โ bu holda res {value, done} bo'ladi.async function* gen() {
yield 1;
yield 2;
}
(async () => {
let g = gen();
console.log(await g.next()); // {value: 1, done: false}
console.log(await g.next()); // {value: 2, done: false}
console.log(await g.next()); // {value: undefined, done: true}
})();
Generator orqali asinxron iterativ obyekt
Sinxron dunyodagidek, asinxron obyektlarni ham generator yordamida ancha soddalashtirish mumkin. Symbol.asyncIterator ni asinxron generator qilib e'lon qilamiz:
let range = {
from: 1,
to: 5,
// async generator sifatida qisqa yozuv
async *[Symbol.asyncIterator]() {
for (let value = this.from; value <= this.to; value++) {
await new Promise(resolve => setTimeout(resolve, 1000));
yield value;
}
}
};
(async () => {
for await (let value of range) {
console.log(value); // 1, 2, 3, 4, 5 (kechikish bilan)
}
})();
Bu variant qo'lda next(), current, last yozishdan ancha qisqa va o'qishga oson. Generator holatni (joriy qiymat, sikl joyi) o'zida avtomatik saqlaydi.
Amaliy misol: sahifama-sahifa ma'lumot yuklash
Asinxron generatorlar eng ko'p sahifalash (pagination) uchun ishlatiladi. Ko'p API'lar ma'lumotni bo'laklarga (sahifalarga) bo'lib beradi: har so'rov bir sahifa qaytaradi, unda keyingi sahifa manzili (URL) bo'ladi. Asinxron generator bu jarayonni chiroyli inkapsulyatsiya qiladi:
// Tasavvur qiling: bu funksiya URL bo'yicha bir sahifani yuklaydi
// va { items: [...], next: 'keyingi-url yoki null' } qaytaradi.
async function* fetchCommits(url) {
while (url) {
const response = await fetch(url); // sahifani so'raymiz
const body = await response.json();
// keyingi sahifa manzilini olamiz
url = body.next || null;
// sahifadagi har bir elementni birma-bir yield qilamiz
for (let item of body.items) {
yield item;
}
}
}
(async () => {
let count = 0;
for await (let commit of fetchCommits('https://api.example.com/commits')) {
console.log(commit);
if (++count >= 100) break; // faqat dastlabki 100 ta kerak
}
})();
Bu kodning go'zalligi shundaki โ foydalanuvchi (for await..of yozgan kod) sahifalash haqida umuman o'ylamaydi. Generator sahifalarni orqa fonda, kerak bo'lganda yuklaydi. break qilinsa, keyingi sahifalar umuman so'ralmaydi โ bu tarmoq trafigini tejaydi.
for await..of keyingi qiymat so'raganda yuklanadi. Foydalanuvchi 100 elementdan keyin to'xtasa, million elementli manba ham to'liq yuklanmaydi.Qachon asinxron iteratsiya kerak?
Asinxron iteratsiyani qachon tanlash kerakligini aniq belgilaymiz:
- Ma'lumot asta-sekin keladi: tarmoq so'rovlari, fayllarni oqim (stream) sifatida o'qish, ma'lumotlar bazasidan katta natijalarni kursor orqali olish;
- Cheksiz yoki noma'lum uzunlikdagi oqim: real vaqt hodisalari, WebSocket xabarlari;
- Sahifalash: API'dan sahifama-sahifa yuklash (yuqoridagi misol);
- Resurslarni tejash: hamma ma'lumotni bir vaqtda xotiraga yuklash o'rniga, kerakli qismini dangasa olish.
Agar ma'lumotingiz allaqachon xotirada tayyor bo'lsa (oddiy massiv, string, Map) โ asinxron iteratsiya kerak emas, oddiy for..of yetarli va tezroq.
Promise) talab qilsa โ asinxron iteratsiya (for await..of, async function*) ishlating. Aks holda โ oddiy iteratsiya.Muhim nozikliklar va tuzoqlar
for await..ofni sinxron funksiya ichida ishlatib bo'lmaydi โ faqatasyncfunksiya ichida yoki top-level modulda;- Spread
...va oddiyfor..ofasinxron iterativ bilan ishlamaydi โ ular sinxronSymbol.iteratorni kutadi; - Asinxron generatorning
next()iPromiseqaytaradi โ qo'lda chaqirsangizawaitqiling; Symbol.asyncIteratorvaSymbol.iteratorni bir obyektga birga qo'yish mumkin โ kontekstga qarab keraklisi tanlanadi.
await ishlatishni unutmang. Agar asinxronlik kerak bo'lmasa, umuman oddiy (sinxron) generator yozing โ async ortiqcha yuk qo'shadi.Xulosa
- Asinxron iteratsiya ma'lumot
Promiselar orqali asta-sekin kelganda ishlatiladi; - Asinxron iterativ obyektda
Symbol.asyncIteratormetodi bo'ladi, uningnext()iPromiseqaytaradi; - Bunday obyektlarni
for await..ofsikli bilan aylanamiz (faqatasyncfunksiya ichida); async function*โ asinxron generator; ichidaawaitishlatish mumkin;- Asinxron generatorlar sahifalash, oqimlar, cheksiz asinxron ketma-ketliklar uchun ideal;
- Spread
...va oddiyfor..ofasinxron iterativlar bilan ishlamaydi; - Ma'lumot allaqachon xotirada bo'lsa โ oddiy iteratsiya yetarli, asinxroni shart emas.