Mundarija (41)
- Bu darsda
- 1. Nega bu kerak?
- 2. Muammo: for...of kutmaydi
- 2.1 Promise'lar massivi
- 2.2 for await...of — har qadamda kutish
- 2.3 Massiv uchun — ortiqcha
- 3. Asinxron iteratsiya protokoli
- 3.1 Sinxron protokolni eslaymiz
- 3.2 Qo'lda yozilgan asinxron iterable
- 3.3 for await ichkarida nima qiladi
- 4. Async generator: async function*
- 4.1 Protokolni qo'lda yozmaslik
- 4.2 Qadamma-qadam: kim kimni kutadi
- 4.3 Async generator nima qaytaradi
- 5. Sahifalangan ma'lumotni oqimga aylantirish
- 5.1 Server o'rinbosari
- 5.2 Sahifalardan bitta oqim
- 5.3 Erta to'xtash: keraksiz sahifa so'ralmaydi
- 5.4 Oqimni tashqaridan to'xtatish
- 6. Array.fromAsync: oqimni massivga yig'ish
- 6.1 Hammasini bitta massivga
- 6.2 Array.fromAsync ketma-ket ishlaydi
- 6.3 Qayerda ishlaydi
- 7. Tayyor asinxron oqimlar
- 7.1 Node: faylni qatorma-qator o'qish
- 7.2 Node: taymer ham oqim bo'ladi
- 7.3 Brauzer va boshqa joylar
- 7.4 Iterator helpers bu yerda yo'q
- 8. Ko'p uchraydigan xatolar
- 8.1 for await oddiy funksiyada
- 8.2 Async generatorni for...of bilan aylanish
- 8.3 Promise'lar massividagi rad etilish
- 8.4 Array.fromAsync ni await siz chaqirish
- 8.5 Har sahifani "parallel" deb o'ylash
- 9. Mashqlar
- 1-mashq (oson): Tartibni ayting
- 2-mashq (o'rta): O'z filtrla yordamchingiz
- 3-mashq (qiyin): Istalgan sahifalangan API uchun oqim
- 10. Real ishda
- Xulosa
- Manbalar
JavaScript asinxron iteratsiya: for await...of, async generator va Array.fromAsync
Qisqacha: Asinxron iteratsiya — qiymatlari vaqt o'tib keladigan ketma-ketlikni oddiy sikl kabi aylanish.
for await (const x of oqim)har qiymatni kutib oladi. Bunday oqimni eng oson yasash yo'li — async generator (async function*): ichida hamawait, hamyieldbor. Hamma qiymatni bitta massivga yig'ish kerak bo'lsa —await Array.fromAsync(oqim).
Bu darsda
for await...ofqachon kerakligini va oddiyfor...ofdan farqini tushuntira olasiz.- Asinxron iteratsiya protokolini —
Symbol.asyncIteratorva Promise qaytaradigannext()ni — bilasiz. async function*bilan sahifalab keladigan ma'lumotni bitta uzluksiz oqimga aylantira olasiz.- Sikldan erta chiqishda (
break) oqim qanday to'xtashini vafinallyqachon ishlashini bilasiz. Array.fromAsyncbilan oqimni massivga yig'a olasiz va Node'dagi tayyor asinxron oqimlarni taniysiz.
Oldin bilishingiz kerak: Iteratsiya protokoli, Generator funksiyalar, async / await, Bekor qilish: AbortController.
1. Nega bu kerak?
«Bahor» oshxonasida Sardor menyu ekranini yozyapti. Menyu serverda saqlanadi va server uni sahifalab beradi: bir so'rovda ikkita taom, keyingi sahifa raqami esa javobning ichida. Nechta sahifa borligini oldindan bilmaysiz — oxirgi sahifada keyingi maydoni null bo'ladi.
Sardor shunday yozmoqchi edi: "har bir taom uchun ekranga qator chiqar". Agar hamma taom massivda tayyor turganda, oddiy for...of yetardi. Lekin taomlar bir vaqtda emas — sahifama-sahifa, har biri bir oz kechikib keladi.
Ikki yo'l bor:
- Avval hamma sahifani yig'ib, keyin chiqarish. Sekin: foydalanuvchi oxirgi sahifagacha bo'sh ekranga qaraydi. Agar sahifalar ko'p bo'lsa — xotira ham to'ladi.
- Sahifalarni so'rash kodini ekranga chiqarish kodi bilan aralashtirish. Ishlaydi, lekin chalkash: "qanday olish" va "nima qilish" bitta joyda.
Bugungi vosita uchinchi yo'lni beradi. "Qanday olish" bitta funksiyada yashirinadi, ishlatadigan kod esa oddiy sikl kabi o'qiladi:
for await (const taom of menyuElementlari()) {
console.log(taom.nom);
}Bu siklni yozish uchun ikkita tanish narsani birlashtiramiz: Generator funksiyalar dagi yield va async / await dagi await.
2. Muammo: for...of kutmaydi
2.1 Promise'lar massivi
Rustam aka ikkita taomni qozonga qo'ydi: osh 300 ms da, lag'mon 100 ms da tayyor bo'ladi. Har taom — Promise. Ularni oddiy for...of bilan aylanamiz:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
const buyurtmalar = [kut(300, "osh"), kut(100, "lag'mon")];
for (const taom of buyurtmalar) {
console.log(taom);
}Konsolda:
Promise { <pending> }
Promise { <pending> }kut — bu qism davomida ko'p ishlatadigan yordamchimiz: ms millisekunddan keyin qiymat bilan bajariladigan Promise (Promise yordamchilari dagi kut(ms) ning qiymatli varianti).
for...of sinxron sikl. U har elementni darhol oladi va kutmaydi. Element esa hali bajarilmagan Promise — konsolda <pending> (kutilmoqda) holati ko'rindi.
2.2 for await...of — har qadamda kutish
Endi sikl oldiga bitta so'z qo'shamiz — await:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
async function buyurtmalarniChiqar() {
const buyurtmalar = [kut(300, "osh"), kut(100, "lag'mon"), "choy"];
for await (const taom of buyurtmalar) {
console.log(taom);
}
console.log("Hammasi berildi");
}
buyurtmalarniChiqar();Konsolda:
osh
lag'mon
choy
Hammasi berildifor await...of — har qadamda elementni await qiladigan sikl. Uch narsaga qarang:
- Tartib saqlandi. Lag'mon oshdan oldin tayyor bo'lgan bo'lsa ham, sikl massiv tartibida yurdi: avval oshni kutdi, keyin lag'mon allaqachon tayyor edi.
- Oddiy qiymat ham bo'ladi.
"choy"— Promise emas.awaituni o'zgarishsiz qaytaradi, xuddiawait 5kabi. - Faqat
asyncfunksiya ichida.for await—awaitning bir turi, shuning uchun u hamasyncfunksiyada yashaydi. Oddiy funksiyada yozsangiz —SyntaxError: Unexpected reserved word. (awaitni funksiyadan tashqarida, modulning eng yuqori qatorida yozish ham mumkin — buni Dinamikimport()va top-levelawaitdarsida ko'ramiz. Hozircha har doimasyncfunksiya ichida yozamiz.)
2.3 Massiv uchun — ortiqcha
Halol aytish kerak: Promise'lar massivi uchun for await shart emas. Xuddi shuni oddiy for...of ichida await taom deb yozsangiz ham bo'ladi. Barchasini birdan kutish kerak bo'lsa — Promise kombinatorlari dagi Promise.all.
for await ning haqiqiy kuchi boshqa joyda: elementlar hali yaratilmagan bo'lganda. Massivdagi Promise'lar oldindan yaratilgan — ikkala qozon allaqachon olovda. Sahifalangan menyuda esa keyingi sahifani so'rash uchun avval oldingisining javobini olish kerak: "keyingi sahifa raqami" o'sha javobda. Bunday ketma-ketlik uchun alohida protokol bor.
Tekshirib ko'ring:
for awaitsiklidagi massivda[kut(500, "A"), kut(100, "B")]bo'lsa,Bnechanchi chiqadi va qachon (taxminan)?
Javob
Ikkinchi, taxminan 500 ms da — A bilan deyarli bir vaqtda. Sikl massiv tartibida yuradi: avval A ni 500 ms kutadi. Shu orada B allaqachon (100 ms da) tayyor bo'lgan, shuning uchun ikkinchi qadamda kutish deyarli yo'q.
3. Asinxron iteratsiya protokoli
3.1 Sinxron protokolni eslaymiz
Iteratsiya protokoli darsida for...of qanday ishlashini ko'rgan edik. Obyektda [Symbol.iterator]() metodi bo'ladi, u iterator qaytaradi. Iteratorning next() metodi har chaqiruvda { value, done } obyektini beradi.
Asinxron protokol xuddi shunday, faqat ikki joyda farq bor:
| Sinxron | Asinxron | |
|---|---|---|
| Kalit metod | [Symbol.iterator]() |
[Symbol.asyncIterator]() |
next() qaytaradi |
{ value, done } |
Promise → { value, done } |
| Sikl | for...of |
for await...of |
Symbol.asyncIterator — Well-known symbols darsidagi jadvalda "11-qismda" deb qoldirgan symbol. Mana, uning navbati keldi.
3.2 Qo'lda yozilgan asinxron iterable
Otabek kassadagi buyurtmalar navbatini kuzatadi. Har 100 ms da bitta yangi buyurtma keladi, uchinchisidan keyin navbat yopiladi. Buni protokol bo'yicha qo'lda yozamiz:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
const navbat = {
[Symbol.asyncIterator]() {
let raqam = 0;
return {
next() {
raqam++;
if (raqam > 3) {
return Promise.resolve({ value: undefined, done: true });
}
return kut(100, { value: `${raqam}-buyurtma`, done: false });
},
};
},
};
async function navbatniKuzat() {
for await (const buyurtma of navbat) {
console.log(buyurtma);
}
console.log("Navbat yopildi");
}
navbatniKuzat();Konsolda:
1-buyurtma
2-buyurtma
3-buyurtma
Navbat yopildiQatorma-qator:
[Symbol.asyncIterator]()—for awaitaynan shu metodni qidiradi. U iterator qaytaradi.let raqam = 0— iteratorning ichki holati.next()uni closure orqali eslaydi (Closure).next()— har safar Promise qaytaradi. Navbat davom etsa, 100 ms dan keyin{ value: "1-buyurtma", done: false }bilan bajariladi.raqam > 3bo'lganda —{ done: true }bilan darhol bajarilgan Promise. Sikl shuni ko'rib to'xtaydi.
3.3 for await ichkarida nima qiladi
Sikl yashirib turgan ishni qo'lda bajaraylik. Shu navbat obyekti bilan:
async function qoldaAylan() {
const iterator = navbat[Symbol.asyncIterator]();
let natija = await iterator.next();
while (!natija.done) {
console.log(natija.value);
natija = await iterator.next();
}
}Bu — for await ning soddalashtirilgan ichki ko'rinishi. Har qadam: next() ni chaqir → Promise'ni await qil → done bo'lsa to'xta, aks holda value bilan sikl tanasini bajar. Keyingi next() faqat oldingisi bajarilgandan keyin chaqiriladi — demak navbat bir vaqtda faqat bitta buyurtmani "tayyorlaydi".
Tekshirib ko'ring: Asinxron iteratorning
next()metodi nima qaytaradi? Javob:
Javob
Promise — u keyinroq { value, done } obyekti bilan bajariladi. Sinxron iteratorda next() shu obyektning o'zini darhol qaytarardi. Asinxron iteratorda esa javob tayyor bo'lishi vaqt oladi, shuning uchun "kelajakdagi natija" — Promise beriladi.
4. Async generator: async function*
4.1 Protokolni qo'lda yozmaslik
Yuqoridagi navbat ishlaydi, lekin 15 qatorning ko'pi "protokol xizmati": obyekt, metod, { value, done }. Generator funksiyalar darsida xuddi shu muammoni function* hal qilgan edi: yield iterator protokolini o'zi bajaradi.
Async generator (asinxron generator) — async function* bilan e'lon qilinadigan funksiya. Ichida ham yield (qiymat ber va to'xta), ham await (kut) ishlatsa bo'ladi. U asinxron iterator qaytaradi — for await uchun tayyor oqim.
O'sha navbat, async generator bilan:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
async function* buyurtmaNavbati() {
for (let raqam = 1; raqam <= 3; raqam++) {
await kut(100);
yield `${raqam}-buyurtma`;
}
}
async function navbatniKuzat() {
for await (const buyurtma of buyurtmaNavbati()) {
console.log(buyurtma);
}
console.log("Navbat yopildi");
}
navbatniKuzat();Konsolda:
1-buyurtma
2-buyurtma
3-buyurtma
Navbat yopildiNatija bir xil, kod esa oddiy siklga o'xshaydi. raqam endi oddiy sikl o'zgaruvchisi, { done: true } ni esa funksiya tugaganda generator o'zi qaytaradi.
4.2 Qadamma-qadam: kim kimni kutadi
Async generator ikki tomonlama "pauza" qiladi: yield da u qiymat berib to'xtaydi, await da esa o'zi biror narsani kutadi. Sikl va generator navbatma-navbat ishlashini kuzating:
Asosiy g'oya: generator dangasa (lazy). U faqat sikl next() so'raganda ishlaydi va yield da to'xtab, keyingi so'rovni kutadi. Sikl tanasi bajarilayotganda generator "uxlaydi" — oldinga yugurib ketmaydi.
4.3 Async generator nima qaytaradi
Generatorni chaqirish uni bajarmaydi — faqat iterator yaratadi. Async generatorda ham shunday:
async function* taomlar() {
yield "osh";
}
const oqim = taomlar();
const tur = Object.prototype.toString.call(oqim);
console.log(tur); // [object AsyncGenerator]
console.log(oqim[Symbol.asyncIterator]() === oqim); // true
console.log(oqim.next()); // Promise { <pending> }- Birinchi qator obyektning turini aniq ko'rsatadi — bu usulni Well-known symbols darsida
Symbol.toStringTagbilan ko'rgansiz. Oqim —AsyncGeneratorobyekti. U bir vaqtda ham asinxron iterator (next()bor), ham asinxron iterable ([Symbol.asyncIterator]()o'zini qaytaradi). Shuning uchun uni to'g'ridan-to'g'rifor awaitga berasiz. next()— Promise. Ichidagi{ value: "osh", done: false }ni olish uchun uniawaitqilish kerak.
Tekshirib ko'ring: Async generator ichida
awaityo'q, faqatyield 1; yield 2;bo'lsa, uni oddiyfor...ofbilan aylansa bo'ladimi?
Javob
Yo'q. async function* har doim asinxron iterator qaytaradi — ichida await bormi-yo'qmi, farqi yo'q. Unda [Symbol.iterator] yo'q, shuning uchun for...of xato beradi: TypeError: ... is not iterable. Bunday generatorni faqat for await bilan aylanasiz.
5. Sahifalangan ma'lumotni oqimga aylantirish
5.1 Server o'rinbosari
Sardorning muammosiga qaytamiz. «Bahor» menyusi kurs uchun ochiq API'da bor: https://ilmhamroh.uz/api/mashq/menyu?sahifa=1&hajm=2 so'rovi shunday javob beradi:
{
"elementlar": [
{ "id": 1, "nom": "Osh", "narx": 35000, "turkum": "taom" },
{ "id": 2, "nom": "Lag'mon", "narx": 28000, "turkum": "taom" }
],
"sahifa": 1,
"keyingi": 2,
"jami": 4
}Serverdan so'rash uchun fetch kerak — uni fetch asoslari darsida o'rganamiz. Hozircha serverni o'zimiz "o'ynaymiz": xuddi shu tuzilmadagi javobni 200 ms dan keyin qaytaradigan funksiya. Ma'lumot ham, javob shakli ham haqiqiy API bilan bir xil:
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
// Server o'rinbosari: 200 ms dan keyin bitta sahifa
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}keyingi — keyingi sahifa raqami yoki, sahifa oxirgi bo'lsa, null. Quyidagi misollarda shu ikki ta'rif blok boshida takrorlanadi — har blok o'zi ishlashi uchun.
5.2 Sahifalardan bitta oqim
Endi async generator yozamiz. U sahifama-sahifa so'raydi va har taomni alohida yield qiladi:
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}
async function* menyuElementlari() {
let sahifa = 1;
while (sahifa !== null) {
console.log(`(${sahifa}-sahifa so'raldi)`);
const javob = await menyuSahifasi(sahifa);
yield* javob.elementlar;
sahifa = javob.keyingi;
}
}
async function menyuniChiqar() {
for await (const taom of menyuElementlari()) {
console.log(`${taom.nom} — ${taom.narx} so'm`);
}
}
menyuniChiqar();Konsolda:
(1-sahifa so'raldi)
Osh — 35000 so'm
Lag'mon — 28000 so'm
(2-sahifa so'raldi)
Manti — 30000 so'm
Ko'k choy — 5000 so'mGenerator ichini o'qiymiz:
while (sahifa !== null)— sahifalar tugaguncha. Nechta sahifa borligini bilish shart emas.await menyuSahifasi(sahifa)— sahifani kutish.yield* javob.elementlar—yield*massivdagi har elementni birma-biryieldqiladi. Async generator ichidayield*oddiy massivni ham, boshqa asinxron oqimni ham qabul qiladi.sahifa = javob.keyingi— keyingi raqam javobning o'zidan. Shuning uchun sahifalarni parallel so'rab bo'lmaydi: ikkinchisining raqami birinchisining javobida.
Endi menyuniChiqar ni qayta o'qing: unda sahifa, keyingi, null haqida bitta so'z ham yo'q. Sahifalash generator ichida yashiringan. Ertaga server sahifalash usulini o'zgartirsa (masalan raqam o'rniga "kursor" — "shu joydan davom et" degan belgi — bersa), faqat generator o'zgaradi.
5.3 Erta to'xtash: keraksiz sahifa so'ralmaydi
Dilshod aka 30 000 so'mdan arzon birinchi taomni so'radi. Uni topgach, sikldan break bilan chiqamiz:
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}
async function* menyuElementlari() {
let sahifa = 1;
try {
while (sahifa !== null) {
console.log(`(${sahifa}-sahifa so'raldi)`);
const javob = await menyuSahifasi(sahifa);
yield* javob.elementlar;
sahifa = javob.keyingi;
}
} finally {
console.log("(oqim yopildi)");
}
}
async function arzoniniTop() {
for await (const taom of menyuElementlari()) {
if (taom.narx < 30000) {
console.log("Topildi:", taom.nom);
break;
}
}
}
arzoniniTop();Konsolda:
(1-sahifa so'raldi)
Topildi: Lag'mon
(oqim yopildi)Ikkinchi sahifa umuman so'ralmadi. Lag'mon birinchi sahifada edi. break dan keyin sikl generatorga "boshqa kerak emas" deb xabar beradi — ichkarida iteratorning return() metodini chaqiradi (buni Generatorlar chuqur darsida qo'lda qilgan edik). Generator yield turgan joyidan "qaytadi", yo'lda esa finally bloki bajariladi.
Bu ikki amaliy natija beradi:
- Tejash. Kerakli narsa topilgach, server qolgan sahifalar uchun bezovta qilinmaydi.
- Tozalash kafolati.
finally— ulanishni yopish, "Yuklanmoqda..." belgisini olib tashlash uchun joy. Siklbreak,returnyoki xato bilan tugasa ham, u bajariladi.
5.4 Oqimni tashqaridan to'xtatish
break faqat siklning ichidan ishlaydi. Lekin ba'zan to'xtash buyrug'i boshqa joydan keladi: foydalanuvchi menyu yuklanayotganda boshqa sahifaga o'tib ketdi. Bekor qilish: AbortController darsidagi signal shu yerda kerak bo'ladi. Generator signalni oladi va har sahifadan oldin ham, keyin ham tekshiradi. Farqni ko'rish uchun har sahifada bittadan taom so'raymiz (hajm = 1):
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}
async function* menyuElementlari(signal) {
let sahifa = 1;
while (sahifa !== null) {
signal.throwIfAborted();
console.log(`(${sahifa}-sahifa so'raldi)`);
const javob = await menyuSahifasi(sahifa, 1);
signal.throwIfAborted();
yield* javob.elementlar;
sahifa = javob.keyingi;
}
}
async function menyuniChiqar() {
const boshqaruv = new AbortController();
setTimeout(() => boshqaruv.abort("Foydalanuvchi ketdi"), 500);
try {
for await (const taom of menyuElementlari(boshqaruv.signal)) {
console.log(taom.nom);
}
} catch (xato) {
console.log("To'xtatildi:", xato);
}
}
menyuniChiqar();Konsolda:
(1-sahifa so'raldi)
Osh
(2-sahifa so'raldi)
Lag'mon
(3-sahifa so'raldi)
To'xtatildi: Foydalanuvchi ketdiVaqt bo'yicha kuzatamiz:
- 200 ms — 1-sahifa keldi (Osh). 400 ms — 2-sahifa (Lag'mon) va darhol 3-sahifa so'raldi.
- 500 ms — signal bekor qilindi. Generator shu payt 3-sahifani kutyapti.
- 600 ms — 3-sahifa keldi, lekin
awaitdan keyingi tekshiruv bekor qilingan signalni ko'rdi va xato tashladi. Manti ekranga chiqmadi.
throwIfAborted() signal bekor qilingan bo'lsa, uning sababini (reason) xato sifatida tashlaydi. Xato generatordan siklga o'tadi va catch ga tushadi.
Ikkinchi tekshiruv nega kerak? U bo'lmasa, 600 ms da Manti baribir chiqardi — foydalanuvchi allaqachon ketgan bo'lsa ham. Uchinchi so'rovning o'zi esa baribir 600 ms gacha davom etdi: bizning o'rinbosar signalni bilmaydi. Haqiqiy fetch signalni qabul qiladi va so'rovni darhol uzadi — buni Request, Response, Headers va fetch opsiyalari va API klient qatlami darslarida ko'ramiz.
Tekshirib ko'ring: Yuqoridagi kodda bekor qilish 500 ms emas, 100 ms da bo'lsa, konsolda nima chiqadi?
Javob
Faqat (1-sahifa so'raldi) va To'xtatildi: Foydalanuvchi ketdi. 1-sahifa 200 ms da keladi, lekin signal 100 ms da bekor qilingan — await dan keyingi tekshiruv xato tashlaydi va Osh ham chiqmaydi.
6. Array.fromAsync: oqimni massivga yig'ish
6.1 Hammasini bitta massivga
Ba'zan har elementni birma-bir emas, hammasini birdan kerak: masalan menyuni narx bo'yicha saralash uchun. Buning uchun sikl yozib, push qilsa bo'ladi. Lekin tayyor vosita bor.
Array.fromAsync(oqim, mapFn) — asinxron oqimni oxirigacha o'qib, elementlarini massivga yig'adi va shu massiv bilan bajariladigan Promise qaytaradi. Bu — Array.from ning asinxron egizagi. Ikkinchi argument (ixtiyoriy) — har elementni o'zgartiradigan funksiya, Array.from dagidek.
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}
async function* menyuElementlari() {
let sahifa = 1;
while (sahifa !== null) {
const javob = await menyuSahifasi(sahifa);
yield* javob.elementlar;
sahifa = javob.keyingi;
}
}
async function narxlarniChiqar() {
const narxlar = await Array.fromAsync(
menyuElementlari(),
(taom) => taom.narx,
);
console.log(narxlar);
console.log(narxlar.toSorted((a, b) => a - b));
}
narxlarniChiqar();Konsolda:
[ 35000, 28000, 30000, 5000 ]
[ 5000, 28000, 30000, 35000 ]await ni unutmang: Array.fromAsync massivni emas, Promise'ni qaytaradi. await siz narxlar o'zgaruvchisida Promise { <pending> } bo'lardi.
6.2 Array.fromAsync ketma-ket ishlaydi
mapFn ham asinxron bo'lishi mumkin. Unda Array.fromAsync har natijani kutadi va shundan keyin keyingi elementga o'tadi. Promise.all esa hammasini birdan boshlaydi. Taqqoslang:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
async function tayyorla(ms) {
console.log("boshlandi", ms);
await kut(ms);
console.log("tugadi", ms);
return ms;
}
async function taqqosla() {
console.log("— Array.fromAsync:");
await Array.fromAsync([300, 100], tayyorla);
console.log("— Promise.all:");
await Promise.all([300, 100].map(tayyorla));
}
taqqosla();Konsolda:
— Array.fromAsync:
boshlandi 300
tugadi 300
boshlandi 100
tugadi 100
— Promise.all:
boshlandi 300
boshlandi 100
tugadi 100
tugadi 300Array.fromAsync — 400 ms (300 + 100), Promise.all — 300 ms (eng uzuni). Qoida: tartib muhim bo'lsa yoki serverga bir vaqtda bitta so'rov yuborish kerak bo'lsa — Array.fromAsync; mustaqil ishlar tez tugashi kerak bo'lsa — Promise.all (Ketma-ket va parallel bajarish).
Diqqat: Cheksiz oqimni
Array.fromAsyncga bermang. U oqim tugashini kutadi — tugamasa, Promise hech qachon bajarilmaydi va xotira to'lib boradi. Cheksiz oqimdan bir qismini olish uchunfor await+break.
6.3 Qayerda ishlaydi
Array.fromAsync — tilga yaqinda qo'shilgan metod (ES2026 standarti). Brauzerlarda: Chrome va Edge 121, Firefox 115, Safari 16.4. 2024 yil yanvarida u "Baseline" bo'ldi — hamma asosiy brauzerlarda bor. 2026 yil iyulidan esa "keng tarqalgan" Baseline maqomida (web-features 3.40 ma'lumoti). Node'da 22-versiyadan bor; darsdagi misollar Node 24 da tekshirilgan. Eski brauzerni qo'llab-quvvatlash kerak bo'lsa — oddiy for await + push.
for await va async generatorlar esa 2018 yildan beri tilda bor (ES2018) va hamma joyda ishlaydi.
7. Tayyor asinxron oqimlar
7.1 Node: faylni qatorma-qator o'qish
Asinxron oqimni har doim o'zingiz yasamaysiz — ko'p tayyor API'lar allaqachon Symbol.asyncIterator ga ega. Eng ko'p uchraydigani — katta faylni qatorma-qator o'qish. Butun faylni xotiraga yuklamasdan, har qator kelishi bilan ishlaydi:
import { createReadStream } from "node:fs";
import { createInterface } from "node:readline";
async function qatorlarniSana() {
const qatorlar = createInterface({
input: createReadStream(import.meta.filename),
});
let soni = 0;
for await (const qator of qatorlar) {
soni++;
if (soni === 1) {
console.log("1-qator:", qator);
}
}
console.log("Jami qatorlar:", soni);
}
qatorlarniSana();Konsolda:
1-qator: import { createReadStream } from "node:fs";
Jami qatorlar: 18Fayl o'zini o'qiydi — Event loop darsidagi kabi, shunchaki o'qiladigan biror fayl kerak edi. Bu yerda createInterface faylni qatorlarga bo'ladigan oqim yasaydi, for await esa har qatorni kutib oladi. Hatto 10 GB lik log fayl ham shu tarzda bir necha megabayt xotira bilan o'qiladi. Buni readline va terminal va Async iteratorlar va Web Streams darslarida chuqur o'rganamiz. Blok faqat Node'da ishlaydi (node:fs), shuning uchun saytda «Ishga tushir» tugmasi yo'q — kompyuteringizda sinang.
7.2 Node: taymer ham oqim bo'ladi
Taymerlar darsidagi sanoqni eslaysizmi? Node'ning node:timers/promises modulidagi setInterval — asinxron oqim: u har oraliqda bitta qiymat beradi.
import { setInterval } from "node:timers/promises";
async function sanoqniBoshla() {
let qoldi = 3;
for await (const _ of setInterval(200)) {
console.log(`${qoldi}...`);
qoldi--;
if (qoldi === 0) {
break;
}
}
console.log("Osh tayyor!");
}
sanoqniBoshla();Konsolda:
3...
2...
1...
Osh tayyor!clearInterval yo'q — break oqimni yopadi, oqim esa intervalni o'zi to'xtatadi. Agar to'xtatmasa, Node dasturi tugamay osilib qolardi. Bu — «Erta to'xtash» bo'limidagi return() va finally ning amaliy foydasi. _ — "bu qiymat kerak emas" degan nom: oqim har safar undefined beradi, bizga faqat ritm kerak.
7.3 Brauzer va boshqa joylar
- Javobni oqim bilan o'qish. Brauzerdagi
ReadableStream(masalan katta faylni yuklash yoki AI chatning so'zma-so'z keladigan javobi) hamfor awaitbilan o'qiladi. Chrome 124, Firefox 110 da bor; Safari'da esa 27-versiyada (2026 yil sentyabr) qo'shildi. Buni Streams API darsida ko'ramiz. - Hodisalar oqimi. Node'dagi
events.on(emitter, "hodisa")har hodisani oqim elementi qiladi — EventEmitter chuqur. - Ma'lumotlar bazasi. Ko'p baza kutubxonalari minglab qatorni bittalab beradigan "kursor"ni (natijada qayerda turganingizni eslab qoladigan ko'rsatkich) async iterable qilib beradi.
7.4 Iterator helpers bu yerda yo'q
Iterator helpers darsidagi map, filter, take — faqat sinxron iteratorlarda. Asinxron oqimda ular hali yo'q:
async function* taomlar() {
yield "osh";
}
console.log(typeof taomlar().map); // undefinedAsinxron variantlari (Async Iterator Helpers) standartga taklif sifatida ko'rib chiqilmoqda, lekin hali tilda yo'q. Hozircha ularni o'zingiz yozasiz — async generator bilan bu bir necha qator. 2-mashqda filtrla ni shunday yozasiz.
8. Ko'p uchraydigan xatolar
8.1 for await oddiy funksiyada
function menyuniChiqar() {
for await (const taom of menyuElementlari()) {
console.log(taom.nom);
}
}SyntaxError: Unexpected reserved word"Kutilmagan zaxiralangan so'z". await — zaxiralangan so'z, uni faqat async funksiya ichida ishlatish mumkin. Xabar await ni ko'rsatadi, lekin xato aslida bir qator yuqorida: funksiya oldida async yo'q. Tuzatish: async function menyuniChiqar().
8.2 Async generatorni for...of bilan aylanish
async function* taomlar() {
yield "osh";
}
const oqim = taomlar();
for (const taom of oqim) {
console.log(taom);
}TypeError: oqim is not iterable"oqim iterable emas". To'g'ri: unda Symbol.iterator yo'q, faqat Symbol.asyncIterator. Generatorni o'zgaruvchisiz, to'g'ridan-to'g'ri yozsangiz (for (const t of taomlar())), xabar boshqacha: taomlar is not a function or its return value is not iterable. Ma'nosi bir xil. Tuzatish: for await.
8.3 Promise'lar massividagi rad etilish
Bu xato yashirin va xavfli. Massivdagi Promise'lardan biri oldinroq rad etilsa:
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
async function taomlarniBer() {
const buyurtmalar = [
kut(300, "osh"),
Promise.reject(new Error("Manti kuyib qoldi")),
];
try {
for await (const taom of buyurtmalar) {
console.log(taom);
}
} catch (xato) {
console.log("Ushlandi:", xato.message);
}
}
taomlarniBer();Kutilgan natija — osh, keyin Ushlandi: Manti kuyib qoldi. Haqiqatda Node dasturni to'xtatadi, osh ham chiqmaydi:
Error: Manti kuyib qolditry/catch bor-ku, nega? Sikl 300 ms oshni kutayotganda rad etilgan Promise "hech kim kutmayotgan" holatda turadi. Node buni ushlanmagan rad etilish deb biladi va dasturni to'xtatadi — sikl unga yetib borishini kutmaydi. Brauzer esa konsolga Uncaught (in promise) Error: Manti kuyib qoldi deb yozadi. Bu holatlarni keyingi dars — Ushlanmagan xatolar — to'liq ochadi.
Tuzatish: oldindan yaratilgan Promise'lar uchun for await emas, Promise.all yoki Promise.allSettled — ular har Promise'ga darhol "kuzatuvchi" ulaydi. Yoki Promise'larni oldindan yaratmang: async generator har birini o'z navbatida yaratadi.
8.4 Array.fromAsync ni await siz chaqirish
const r = Array.fromAsync(oqim) — r massiv emas, Promise { <pending> }. r.length — undefined, r.map — xato. Tuzatish: await va async funksiya.
8.5 Har sahifani "parallel" deb o'ylash
Async generator ketma-ket ishlaydi: keyingi sahifa faqat sikl navbatdagi elementni so'raganda so'raladi. Bu tejamkor, lekin tez emas. Agar sahifalar soni oldindan ma'lum bo'lsa (jami va hajm dan hisoblash mumkin) va hammasi kerak bo'lsa — Promise.all bilan parallel so'rash tezroq. Generator — "qancha kerakligini bilmayman" yoki "ko'p so'rov serverni qiynaydi" holatlari uchun.
9. Mashqlar
1-mashq (oson): Tartibni ayting
Ishga tushirishdan oldin chiqishni yozing. Ishora: hisobla() — async funksiya, u birinchi await da to'xtab, boshqaruvni chaqirgan kodga qaytaradi (async / await).
async function* raqamlar() {
yield 10;
yield 20;
yield 30;
}
async function hisobla() {
let summa = 0;
for await (const raqam of raqamlar()) {
summa += raqam;
}
console.log("Summa:", summa);
}
console.log("A");
hisobla();
console.log("B");Yechim
Konsolda:
A
B
Summa: 60hisobla() chaqirilganda sikl birinchi next() ni kutib to'xtaydi — generatorda await bo'lmasa ham, for await har qadamni Promise orqali o'tkazadi. Shuning uchun hisobla() darhol qaytadi va B chiqadi. Qolgan qadamlar microtask'larda bajariladi va Summa: 60 eng oxirida keladi.
2-mashq (o'rta): O'z filtrla yordamchingiz
Asinxron oqimlarda filter yo'q. Async generator filtrla(oqim, shart) ni yozing: u oqim ning faqat shart(element) true bo'lgan elementlarini yield qilsin. Sinov uchun buyurtmalar() generatori har 100 ms da bitta buyurtma beradi; faqat 30 000 so'mdan qimmatlarini chiqaring.
async function* buyurtmalar() {
const royxat = [
{ taom: "Osh", narx: 35000 },
{ taom: "Ko'k choy", narx: 5000 },
{ taom: "Manti", narx: 30000 },
{ taom: "Osh", narx: 35000 },
];
for (const b of royxat) {
await kut(100);
yield b;
}
}kut — dars boshidagi yordamchi. Faylni kurs/mashqlar/11/13-asinxron-iteratsiya/filtrla.mjs ga saqlang.
Yechim
function kut(ms, qiymat) {
return new Promise((resolve) => setTimeout(resolve, ms, qiymat));
}
async function* buyurtmalar() {
const royxat = [
{ taom: "Osh", narx: 35000 },
{ taom: "Ko'k choy", narx: 5000 },
{ taom: "Manti", narx: 30000 },
{ taom: "Osh", narx: 35000 },
];
for (const b of royxat) {
await kut(100);
yield b;
}
}
async function* filtrla(oqim, shart) {
for await (const element of oqim) {
if (shart(element)) {
yield element;
}
}
}
async function qimmatlarniChiqar() {
const qimmatlar = filtrla(buyurtmalar(), (b) => b.narx > 30000);
for await (const b of qimmatlar) {
console.log(b.taom, b.narx);
}
}
qimmatlarniChiqar();Konsolda:
Osh 35000
Osh 35000filtrla ham async generator: u boshqa oqimni for await bilan o'qiydi va o'zi yangi oqim beradi. Ikkalasi ham dangasa — qimmatlarniChiqar keyingi elementni so'ramaguncha hech kim ishlamaydi. Manti > 30000 shartidan o'tmadi: 30 000 — chegara, "dan qimmat" emas. Shu usul bilan xarita (map) va birinchilari (take) ni ham yozish mumkin.
git add 11/13-asinxron-iteratsiya/filtrla.mjs
git commit -m "11/13: asinxron oqim uchun filtrla"3-mashq (qiyin): Istalgan sahifalangan API uchun oqim
Sardor boshqa API'lar ham sahifalab berishini bildi: buyurtmalar tarixi, mijozlar ro'yxati. Har biri uchun alohida generator yozmaslik uchun umumiy funksiya kerak.
sahifalabOl(sahifaniOl) async generatorini yozing. sahifaniOl(raqam) — Promise qaytaradigan funksiya, natijasi { elementlar, keyingi } (masalan dars davomidagi menyuSahifasi). Generator keyingi null bo'lguncha hamma elementlarni bersin va har so'rovdan oldin (N-sahifa) deb yozsin.
Sinov: menyuSahifasi ni hajm = 1 bilan ishlating ((raqam) => menyuSahifasi(raqam, 1)) va for await + break bilan faqat birinchi ikki taomni oling. Konsolda faqat ikkita sahifa so'rovi ko'rinishi kerak. Javobdan kerakli maydonlarni olish uchun destructuring qulay: const { elementlar, keyingi } = javob (Obyekt destructuring). Faylni kurs/mashqlar/11/13-asinxron-iteratsiya/sahifalar.mjs ga saqlang.
Yechim
const MENYU = [
{ id: 1, nom: "Osh", narx: 35000 },
{ id: 2, nom: "Lag'mon", narx: 28000 },
{ id: 3, nom: "Manti", narx: 30000 },
{ id: 4, nom: "Ko'k choy", narx: 5000 },
];
function menyuSahifasi(sahifa, hajm = 2) {
const boshi = (sahifa - 1) * hajm;
const javob = {
elementlar: MENYU.slice(boshi, boshi + hajm),
sahifa,
keyingi: boshi + hajm < MENYU.length ? sahifa + 1 : null,
jami: MENYU.length,
};
return new Promise((resolve) => setTimeout(resolve, 200, javob));
}
async function* sahifalabOl(sahifaniOl) {
let raqam = 1;
while (raqam !== null) {
console.log(`(${raqam}-sahifa)`);
const { elementlar, keyingi } = await sahifaniOl(raqam);
yield* elementlar;
raqam = keyingi;
}
}
async function ikkitasiniOl() {
const oqim = sahifalabOl((raqam) => menyuSahifasi(raqam, 1));
const olingan = [];
for await (const taom of oqim) {
olingan.push(taom.nom);
if (olingan.length === 2) {
break;
}
}
console.log(olingan);
}
ikkitasiniOl();Konsolda:
(1-sahifa)
(2-sahifa)
[ 'Osh', "Lag'mon" ]Uchinchi va to'rtinchi sahifalar so'ralmadi: ikkinchi taomdan keyin break oqimni yopdi. sahifalabOl hech qaysi API'ni bilmaydi — u faqat "sahifa ber" funksiyasini oladi. Bu — Callback va HOF g'oyasining asinxron oqimdagi ko'rinishi. fetch asoslari dan keyin sahifaniOl o'rniga haqiqiy https://ilmhamroh.uz/api/mashq/menyu?sahifa=…&hajm=1 so'rovini berasiz — generator o'zgarmaydi.
git add 11/13-asinxron-iteratsiya/sahifalar.mjs
git commit -m "11/13: umumiy sahifalab olish generatori"10. Real ishda
- Cheksiz lenta (infinite scroll). Telegram kanalining eski postlari, onlayn do'kon katalogi — pastga aylantirganda keyingi sahifa keladi. Ichida — sahifalab oladigan oqim va "yana bitta sahifa ber" so'rovi.
- API kutubxonalari. Katta xizmatlarning rasmiy JavaScript kutubxonalari (to'lov tizimlari, bulut xizmatlari) ro'yxatlarni ko'pincha async iterable qilib beradi:
for await (const tolov of mijoz.tolovlar.royxat())— sahifalashni kutubxona o'zi bajaradi. - AI javoblari. Chat-bot javobi so'zma-so'z keladi; uni o'qish —
for awaitsiklidagi oqim (Streams API). - Node server. Log fayllarni tahlil qilish, CSV import, bazadan million qatorni eksport — xotirani to'ldirmaslik uchun hammasi oqim bilan.
- Intervyu. "Async generator nima va oddiy generatordan farqi?", "Sahifalangan API'ni qanday aylanasiz?" — o'rta darajadagi frontend va Node intervyularida uchraydi.
Xulosa
for await...of— har elementni kutib aylanadigan sikl; faqatasyncfunksiya ichida.- Asinxron protokol:
[Symbol.asyncIterator]()iterator qaytaradi, iteratorningnext()i Promise beradi. async function*— ichidaawaitvayieldbor; protokolni o'zi bajaradi va dangasa ishlaydi.breakoqimni yopadi: generatorfinallyni bajaradi, keraksiz sahifalar so'ralmaydi. Tashqaridan to'xtatish —AbortSignal.Array.fromAsync— oqimni massivga yig'adi, ketma-ket ishlaydi, cheksiz oqim uchun emas.- Oldindan yaratilgan Promise'lar massivi uchun
for awaitemas —Promise.allyokiPromise.allSettled.
Keyingi dars: Ushlanmagan xatolar va global xato ushlash — «Ko'p uchraydigan xatolar» dagi "hech kim kutmagan" rad etilish nima bo'lishini, brauzerdagi unhandledrejection va error hodisalarini va Node'ning process.on ini o'rganamiz.
Manbalar
- ECMAScript: "AsyncGenerator Objects", "for-in, for-of, and for-await-of Statements",
Array.fromAsync— tc39.es/ecma262 - MDN: "for await...of", "async function*", "Symbol.asyncIterator", "Array.fromAsync()" — developer.mozilla.org
- Node.js 24 hujjatlari: "Readline" (
for awaitbilan qatorlar), "Timers Promises API" (setInterval) — nodejs.org/api - web-features 3.40:
array-fromasync,async-iterators,async-iterable-streams— github.com/web-platform-dx/web-features
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!