IlmHamroh
JavaScript Full-stack/11-qism. Asinxron JavaScript va tarmoq9/32-dars22 daqiqa
Mundarija (34)

Promise kombinatorlari: Promise.all, allSettled, race va any

Qisqacha: To'rtta kombinator bir nechta Promise'ni bitta Promise'ga birlashtiradi, lekin har biri boshqa savolga javob beradi. Masalan, Promise.all — "hammasi muvaffaqiyatli bo'ldimi?", birinchi xatoda rad etiladi; Promise.allSettled — "har biri qanday tugadi?", hech qachon rad etilmaydi. Poyga uchun ikkitasi bor: Promise.race birinchi hal bo'lganini oladi (xato bo'lsa ham), Promise.any esa birinchi muvaffaqiyatlisini. Hammasi rad etilsa, any — AggregateError beradi.

Bu darsda

  • Promise.all ning natija tartibini, "birinchi xatoda rad" qoidasini va chegaraviy holatlarini aniq bilasiz.
  • Promise.allSettled bilan har bir ishning natijasini — muvaffaqiyat ham, xato ham — yo'qotmasdan yig'asiz.
  • Promise.race bilan vaqt chegarasi (timeout) yozasiz va uning tuzog'ini bilasiz.
  • Promise.any va AggregateError ni ishlatib, "birinchi muvaffaqiyatli javob" naqshini yozasiz.
  • Vaziyatga qarab to'rttasidan to'g'risini tanlay olasiz.

Oldin bilishingiz kerak: Promise asoslari, Promise zanjiri, async / await, Ketma-ket va parallel bajarish.

1. Nega bu kerak?

Callbacklar darsida ikki zaldan bo'sh stollarni bir vaqtda so'ragan edik. Buning uchun qoldi hisoblagichi, javoblar[indeks] va xato uchun yana bitta bayroq kerak bo'lgan edi. O'shanda "kombinatorlar buni bitta qator bilan bajaradi" degan edik. Ketma-ket va parallel bajarish darsida birinchisini — Promise.all ni — ko'rdingiz.

Lekin «Bahor»da parallel ishlar har xil bo'ladi va har biri boshqa savol beradi:

Vaziyat Savol
Stolga osh, salat va choy birga chiqadi Hammasi tayyormi? Bittasi bo'lmasa — buyurtma bo'lmaydi
Kun oxirida uch filialdan hisobot Har filial qanday tugatdi? Bittasi javob bermasa ham, qolganlari kerak
Oshxona javobini 1 soniyadan ko'p kutmaymiz Nima birinchi bo'ldi: javobmi yoki muddat?
Uch zaldan bittasida joy topish kerak Birinchi "joy bor" degan qaysi?

To'rt savol — to'rt kombinator. Kombinator — bir nechta Promise'ni qabul qilib, ulardan bitta yangi Promise yasaydigan funksiya. Uning natijasi qachon va qanday hal bo'lishi — kombinatorning qoidasiga bog'liq.

Xuddi oshxonadagi to'rt xil ofitsiant kabi. Biri hamma taom tayyor bo'lishini kutadi. Ikkinchisi har taom haqida xabar yig'adi. Uchinchisi birinchi signal bilan yuguradi. To'rtinchisi birinchi tayyor taomni olib chiqadi.

1.1 Darsdagi yordamchi funksiya

Hamma misollarda oshxonani bitta funksiya bilan taqlid qilamiz — haqiqiy so'rov (fetch) fetch asoslari darsida keladi:

js
function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (xato) {
        reject(new Error(xato));
        return;
      }
      console.log(`  ${taom} tayyor`);
      resolve(taom);
    }, ms);
  });
}

tayyorla("osh", 200).then((taom) => console.log("Keldi:", taom));

Konsolda:

text
  osh tayyor
Keldi: osh

tayyorla ms millisekunddan keyin taomni beradi va "tayyor" deb yozadi. Uchinchi argument — xato matni — berilsa, Promise shu matnli xato bilan rad etiladi. "Tayyor" yozuvlari ikki bo'shliq bilan surilgan: ular oshxona ichida bo'layotgan ishni ko'rsatadi.

2. Promise.all: hammasi yoki hech narsa

2.1 Natija — berilgan tartibda

Promise.all(promiselar) — massivdagi hamma Promise bajarilishini kutadi va natijalarni massiv qilib beradi. Uni avvalgi darsdan bilasiz. Bugun uni oxirigacha ochamiz:

js
function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (xato) {
        reject(new Error(xato));
        return;
      }
      console.log(`  ${taom} tayyor`);
      resolve(taom);
    }, ms);
  });
}

async function stolgaChiqar() {
  const taomlar = await Promise.all([
    tayyorla("osh", 300),
    tayyorla("salat", 100),
    tayyorla("choy", 200),
  ]);
  console.log("Stolga:", taomlar.join(", "));
}

stolgaChiqar();

Konsolda:

text
  salat tayyor
  choy tayyor
  osh tayyor
Stolga: osh, salat, choy

Ikki narsaga qarang. Taomlar har xil tartibda tayyor bo'ldi: salat, choy, osh. Natija massivi esa berilgan tartibda: osh, salat, choy. Promise.all har natijani o'z o'rniga qo'yadi — xuddi Callbacklar darsidagi javoblar[indeks] kabi. Shuning uchun taomlar[0] doim osh, kim birinchi tayyor bo'lishidan qat'i nazar.

Umumiy vaqt — eng sekin ish vaqti, ya'ni taxminan 300 ms. Ketma-ket await bilan 600 ms bo'lardi.

2.2 Birinchi xatoda — darhol rad

Endi salat uchun pomidor tugagan:

js
function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (xato) {
        reject(new Error(xato));
        return;
      }
      console.log(`  ${taom} tayyor`);
      resolve(taom);
    }, ms);
  });
}

async function stolgaChiqar() {
  try {
    const taomlar = await Promise.all([
      tayyorla("osh", 300),
      tayyorla("salat", 100, "Pomidor tugadi"),
      tayyorla("choy", 200),
    ]);
    console.log("Stolga:", taomlar.join(", "));
  } catch (xato) {
    console.log("Buyurtma bajarilmadi:", xato.message);
  }
}

stolgaChiqar();

Konsolda:

text
Buyurtma bajarilmadi: Pomidor tugadi
  choy tayyor
  osh tayyor

Salat 100 ms da rad etildi va shu zahoti Promise.all ham rad etildi — choy va oshni kutmadi. Bu qoida fail-fast ("tez yiqilish") deb ataladi: natija baribir bo'lmasa, kutishdan foyda yo'q. Xato catch ga birinchi rad etilgan Promise'ning xatosi sifatida keladi.

Lekin pastki ikki qatorga qarang. "Buyurtma bajarilmadi" dan keyin ham choy va osh tayyor bo'ldi. Qadamma-qadam ko'ramiz:

Bu muhim farq: Promise.all kutishni to'xtatadi, ishlarni emas. Promise — faqat natija uchun "chipta", u ishni boshqarmaydi. Oshpaz osh pishirishda davom etadi. Haqiqiy so'rovlarni to'xtatish uchun boshqa vosita kerak — AbortController darsida ko'ramiz.

2.3 Chegaraviy holatlar

Uchta kichik, lekin intervyuda so'raladigan detal. Kodda bitta yangi yozuv bor: Promise.resolve(28000) — new Promise((resolve) => resolve(28000)) ning qisqa shakli, ya'ni darhol bajarilgan Promise. Uni keyingi darsda, Promise yordamchilari da to'liq ko'ramiz.

js
async function sinab() {
  const bosh = await Promise.all([]);
  console.log(bosh);

  const aralash = await Promise.all([35000, Promise.resolve(28000)]);
  console.log(aralash);

  const toplam = new Set([Promise.resolve("osh")]);
  console.log(await Promise.all(toplam));
}

sinab();

Konsolda:

text
[]
[ 35000, 28000 ]
[ 'osh' ]
  • Bo'sh massiv — darhol [] bilan bajariladi. Kutadigan narsa yo'q — demak "hammasi tayyor".
  • Promise bo'lmagan qiymat (35000) — tayyor natija deb qabul qilinadi va o'z o'rniga qo'yiladi.
  • Massiv shart emas — har qanday iterable bo'ladi: Set, Map ning values(), generator (Iterable va iterator protokoli). Natija esa doim massiv.

Tekshirib ko'ring: Promise.all([tayyorla("osh", 500), tayyorla("choy", 100, "Choy tugadi")]) taxminan necha millisekundda va qanday tugaydi? Osh tayyor bo'ladimi?

Javob

Taxminan 100 ms da, "Choy tugadi" xatosi bilan rad etiladi — fail-fast. Osh baribir 500 ms da tayyor bo'ladi va "osh tayyor" deb yozadi: Promise.all ishni to'xtatmaydi, faqat uning natijasini kutmay qo'yadi.

3. Promise.allSettled: har birining taqdiri

3.1 Hech qachon rad etilmaydi

Kun oxirida «Bahor»ning uch filialidan bugungi tushum so'raladi. Yunusobod filiali javob bermadi. Promise.all bo'lganida qolgan ikkitasining raqami ham yo'qolardi. Hisobotga esa hammasi kerak: kim qancha topdi va kim javob bermadi.

Promise.allSettled(promiselar) — hamma Promise hal bo'lishini (settled — bajarildi yoki rad etildi) kutadi. U hech qachon rad etilmaydi. Natija — har Promise uchun bitta hisobot obyekti:

  • bajarilgan bo'lsa — { status: "fulfilled", value: natija };
  • rad etilgan bo'lsa — { status: "rejected", reason: xato }.
js
function tushumniSora(filial, ms, summa) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (summa === undefined) {
        reject(new Error(`${filial}: aloqa yo'q`));
        return;
      }
      resolve(summa);
    }, ms);
  });
}

async function hisobotTuz() {
  const filiallar = ["Chilonzor", "Yunusobod", "Sergeli"];
  const natijalar = await Promise.allSettled([
    tushumniSora("Chilonzor", 200, 4_200_000),
    tushumniSora("Yunusobod", 100),
    tushumniSora("Sergeli", 300, 3_100_000),
  ]);

  console.log(natijalar.map((n) => n.status));

  let jami = 0;
  natijalar.forEach((natija, i) => {
    if (natija.status === "fulfilled") {
      jami += natija.value;
      console.log(`${filiallar[i]}: ${natija.value} so'm`);
    } else {
      console.log(`${filiallar[i]}: ${natija.reason.message}`);
    }
  });
  console.log("Jami:", jami);
}

hisobotTuz();

Konsolda:

text
[ 'fulfilled', 'rejected', 'fulfilled' ]
Chilonzor: 4200000 so'm
Yunusobod: Yunusobod: aloqa yo'q
Sergeli: 3100000 so'm
Jami: 7300000

Yunusobod 100 ms da rad etildi, lekin allSettled kutishda davom etdi va 300 ms da — oxirgi filial javob bergach — hisobotni berdi. Natijalar ham Promise.all dagidek berilgan tartibda.

E'tibor bering: try/catch kerak bo'lmadi. allSettled ning o'zi rad etilmaydi — xatolar natija ichida, reason maydonida keladi. Shuning uchun har natijaning status ini tekshirish sizning vazifangiz.

3.2 Muvaffaqiyatlilarni ajratish

Ko'pincha natijalarni ikki guruhga ajratish kerak bo'ladi — filter va map bilan:

js
const natijalar = [
  { status: "fulfilled", value: 4_200_000 },
  { status: "rejected", reason: new Error("aloqa yo'q") },
  { status: "fulfilled", value: 3_100_000 },
];

const summalar = natijalar
  .filter((n) => n.status === "fulfilled")
  .map((n) => n.value);
const xatolar = natijalar
  .filter((n) => n.status === "rejected")
  .map((n) => n.reason.message);

console.log(summalar); // [ 4200000, 3100000 ]
console.log(xatolar); // [ "aloqa yo'q" ]

Bu yerda natijalarni qo'lda yozdik — allSettled aynan shunday massiv beradi. Node satrni odatda '...' ichida ko'rsatadi, lekin satrning o'zida ' bo'lsa — "..." ichida. Ma'nosi bir xil.

Promise.allSettled ES2020 da qo'shilgan; Baseline ma'lumotiga ko'ra 2020-yil iyulidan hamma asosiy brauzerda bor.

Tekshirib ko'ring: Promise.allSettled ga berilgan uchala Promise ham rad etilsa, await Promise.allSettled(...) qatori xato tashlaydimi?

Javob

Yo'q. allSettled hech qachon rad etilmaydi. U uchta { status: "rejected", reason: ... } obyektli massiv bilan bajariladi. Xatoni bilish uchun status ni o'zingiz tekshirasiz.

4. Promise.race: birinchi hal bo'lgan yutadi

4.1 Poyga

Promise.race(promiselar) — birinchi hal bo'lgan Promise'ning natijasini oladi. Birinchi bo'lib bajarilgan bo'lsa — o'sha qiymat bilan bajariladi. Birinchi bo'lib rad etilgan bo'lsa — o'sha xato bilan rad etiladi. "Race" — inglizcha "poyga".

js
function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (xato) {
        reject(new Error(xato));
        return;
      }
      console.log(`  ${taom} tayyor`);
      resolve(taom);
    }, ms);
  });
}

async function poyga() {
  const birinchi = await Promise.race([
    tayyorla("osh", 300),
    tayyorla("somsa", 100),
  ]);
  console.log("Birinchi chiqdi:", birinchi);

  try {
    await Promise.race([
      tayyorla("manti", 300),
      tayyorla("lag'mon", 100, "Xamir tugadi"),
    ]);
  } catch (xato) {
    console.log("Poyga xato bilan tugadi:", xato.message);
  }
}

poyga();

Konsolda:

text
  somsa tayyor
Birinchi chiqdi: somsa
Poyga xato bilan tugadi: Xamir tugadi
  osh tayyor
  manti tayyor

Birinchi poygada somsa yutdi. Ikkinchisida lag'mon birinchi rad etildi — va race ham rad etildi, garchi manti muvaffaqiyatli tayyor bo'lsa ham. race uchun muvaffaqiyat yoki xato — farqi yo'q, faqat kim birinchi. Yutqazganlar esa Promise.all dagidek ishlashda davom etadi: osh ham, manti ham tayyor bo'ldi.

4.2 Asosiy qo'llanishi: vaqt chegarasi

race ning eng ko'p uchraydigan ishi — vaqt chegarasi (timeout): "javob 1 soniyada kelmasa, kutmayman". Ishni muddat bilan poygaga qo'yamiz. Muddat — belgilangan vaqtda rad etiladigan Promise:

js
function tayyorla(taom, ms) {
  return new Promise((resolve) => {
    setTimeout(() => resolve(taom), ms);
  });
}

function muddatBilan(promise, ms) {
  let taymer;
  const muddat = new Promise((resolve, reject) => {
    taymer = setTimeout(() => {
      reject(new Error(`${ms} ms da javob kelmadi`));
    }, ms);
  });
  return Promise.race([promise, muddat]).finally(() => {
    clearTimeout(taymer);
  });
}

async function buyurtmaBer(taom, ms) {
  try {
    const natija = await muddatBilan(tayyorla(taom, ms), 250);
    console.log("Olib chiqildi:", natija);
  } catch (xato) {
    console.log(`${taom}: ${xato.message}`);
  }
}

async function sinab() {
  await buyurtmaBer("somsa", 100);
  await buyurtmaBer("osh", 400);
}

sinab();

Konsolda:

text
Olib chiqildi: somsa
osh: 250 ms da javob kelmadi

Kod qanday ishlaydi: muddat — ms dan keyin rad etiladigan Promise. Keyin race ish va muddatni poygaga qo'yadi. Somsa 100 ms da keldi — ish yutdi. Osh 400 ms oladi — muddat (250 ms) yutdi. Ikkala holatda ham finally taymerni tozalaydi. O'zgaruvchi taymer executor'dan tashqarida e'lon qilingan, chunki finally ichida ham unga yetish kerak.

Callbacklar darsining 3-mashqida xuddi shu nomli muddatBilan ni yozgan edingiz — u yerda tugadi bayrog'i va birMarta himoyasi kerak edi. Promise'da ular kerak emas: Promise bir marta hal bo'ladi, keyingi resolve/reject chaqiruvlari e'tiborsiz qoladi (Promise asoslari).

4.3 Tuzoq: taymerni tozalash

finally dagi clearTimeout ni olib tashlasangiz, somsa misoli ham to'g'ri chiqadi. Lekin muddat taymeri 250 ms gacha "osilib" qoladi. Shu vaqtda u:

  • Node'da dasturning tugashini kechiktiradi — Node faol taymer bor ekan, jarayonni yopmaydi;
  • reject ni chaqiradi — natija hech kimga kerak emas, lekin ish bajariladi.

Bitta so'rovda bu sezilmaydi. Sekundiga yuzlab so'rov qabul qiladigan serverda esa minglab ortiqcha taymer to'planadi. Qoida: poygaga qo'ygan taymeringizni o'zingiz tozalang. Brauzer va Node'da buning tayyor vositasi ham bor — AbortSignal.timeout(ms) — uni AbortController darsida ko'ramiz.

4.4 Bo'sh poyga

Promise.race([]) hech qachon hal bo'lmaydi — u abadiy kutish holatida qoladi. Poygachi yo'q — g'olib ham yo'q. Massiv dinamik yig'ilsa (masalan filter dan keyin), bo'sh bo'lib qolmasligini tekshiring: aks holda await abadiy kutadi va xato ham chiqmaydi.

Tekshirib ko'ring: muddatBilan(tayyorla("osh", 100, "Guruch tugadi"), 250) nima bilan tugaydi: "Guruch tugadi" bilanmi yoki "250 ms da javob kelmadi" bilanmi?

Javob

"Guruch tugadi" bilan, taxminan 100 ms da. race birinchi hal bo'lganini oladi — rad etilish ham hal bo'lish. Muddat Promise'i 250 ms da hal bo'lardi, lekin finally ichidagi clearTimeout uni bekor qiladi.

5. Promise.any: birinchi muvaffaqiyatli

5.1 Xatolarni o'tkazib yuboradi

Dilshod aka 6 kishilik stol so'radi. Uch zal bor — ichki zal, ayvon va yozgi supa. Qaysi biri birinchi "joy bor" desa — o'sha yerga bron qilamiz. "Joy yo'q" javoblari bizni qiziqtirmaydi.

race bu yerda yaramaydi: birinchi bo'lib "joy yo'q" kelsa, race shu xato bilan tugaydi. Bizga birinchi muvaffaqiyatli javob kerak.

Promise.any(promiselar) — birinchi bajarilgan Promise'ning qiymatini oladi. Rad etilganlarni e'tiborsiz qoldiradi va keyingisini kutadi:

js
function stolSora(zal, ms, bormi) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (bormi) {
        resolve(`${zal}: 6 kishilik stol bor`);
      } else {
        reject(new Error(`${zal}: joy yo'q`));
      }
    }, ms);
  });
}

async function stolTop() {
  const javob = await Promise.any([
    stolSora("ichki zal", 100, false),
    stolSora("ayvon", 300, true),
    stolSora("yozgi supa", 200, true),
  ]);
  console.log("Bron:", javob);
}

stolTop();

Konsolda:

text
Bron: yozgi supa: 6 kishilik stol bor

Ichki zal 100 ms da "joy yo'q" dedi — any e'tibor bermadi. Yozgi supa 200 ms da "bor" dedi — any shu javob bilan bajarildi. Ayvon javobi (300 ms) kerak bo'lmadi.

5.2 Hammasi rad etilsa: AggregateError

Uchala zal ham to'la bo'lsa-chi? any qaysi xatoni berishi kerak — birinchisinimi, oxirgisinimi? Hech birini emas: u hamma xatoni bitta obyektga yig'adi.

AggregateError ("yig'ma xato") — bir nechta xatoni errors massivida saqlaydigan xato turi. Stack trace va JavaScript xato turlari darsida "uni haqiqiy chiqishda ko'rasiz" degan edik. Mana u:

js
function stolSora(zal, ms) {
  return new Promise((resolve, reject) => {
    setTimeout(() => reject(new Error(`${zal}: joy yo'q`)), ms);
  });
}

async function stolTop() {
  try {
    await Promise.any([
      stolSora("ichki zal", 100),
      stolSora("ayvon", 300),
      stolSora("yozgi supa", 200),
    ]);
  } catch (xato) {
    console.log(xato.name);
    console.log(xato.message);
    console.log(xato.errors.map((x) => x.message));
  }
}

stolTop();

Konsolda:

text
AggregateError
All promises were rejected
[ "ichki zal: joy yo'q", "ayvon: joy yo'q", "yozgi supa: joy yo'q" ]

All promises were rejected — "hamma Promise'lar rad etildi". errors massividagi xatolar ham berilgan tartibda, rad etilish vaqti tartibida emas: ayvon 300 ms da oxirgi bo'lib rad etildi, lekin massivda ikkinchi.

Agar catch bo'lmasa, Node 24 dasturni to'xtatadi va terminalda xato [AggregateError: All promises were rejected] { qatori bilan boshlanadi. Uning ichida [errors]: [ ro'yxati va har xatoning stack trace'i keladi.

Promise.any([]) — bo'sh massiv — darhol AggregateError bilan rad etiladi: muvaffaqiyatli bo'ladigan birorta ham Promise yo'q.

Promise.any va AggregateError ES2021 da qo'shilgan; Baseline ma'lumotiga ko'ra 2020-yil sentyabridan hamma asosiy brauzerda bor.

Tekshirib ko'ring: Promise.any([tayyorla("osh", 300), tayyorla("choy", 100, "Choy tugadi")]) nima bilan tugaydi? Promise.race bilan-chi?

Javob

any — 300 ms da "osh" bilan bajariladi: choyning xatosi o'tkazib yuboriladi. race — 100 ms da "Choy tugadi" xatosi bilan rad etiladi: u birinchi hal bo'lganini oladi, xato bo'lsa ham.

6. Qaysi vaziyatda qaysi?

6.1 Taqqoslash jadvali

To'rttasi bir xil shaklda chaqiriladi, lekin qoidalari har xil:

Kombinator Qachon bajariladi Qachon rad etiladi
Promise.all hammasi bajarilsa → qiymatlar massivi birinchi rad etilishda
Promise.allSettled hammasi hal bo'lsa → hisobotlar massivi hech qachon
Promise.race birinchi hal bo'lgan bajarilsa birinchi hal bo'lgan rad etilsa
Promise.any birinchi bajarilganda hammasi rad etilsa → AggregateError

Bo'sh massiv bilan ham xulqi har xil: all va allSettled darhol [] beradi, any darhol AggregateError, race esa abadiy kutadi.

Hayotiy savollar bilan bog'lasak — shunday tanlash sxemasi chiqadi:

flowchart TD
  S{"Hamma natija<br/>kerakmi?"}
  S -- "ha" --> X{"Bitta xato hammasini<br/>buzadimi?"}
  X -- "ha" --> A["Promise.all"]
  X -- "yo'q" --> B["Promise.allSettled"]
  S -- "yo'q, bittasi yetadi" --> Y{"Xato javob ham<br/>javobmi?"}
  Y -- "ha, kim birinchi" --> C["Promise.race"]
  Y -- "yo'q, muvaffaqiyatli kerak" --> D["Promise.any"]

6.2 Hayotiy misollar

  • all — sahifa uchun menyu, foydalanuvchi va savat birga kerak; bittasisiz sahifani ko'rsatib bo'lmaydi. Yoki 10 ta rasmni yuklash, keyin galereyani chizish.
  • allSettled — bir nechta mustaqil ish: 50 ta mijozga SMS yuborish va "45 ta yuborildi, 5 ta xato" hisoboti. Yoki boshqaruv panelidagi vidjetlar: biri yuklanmasa, qolganlari ko'rinaversin.
  • race — vaqt chegarasi. Yoki "eng tez javob bergan" holati, xato ham javob hisoblanganda.
  • any — bir xil ma'lumotning bir nechta manbasi (zaxira serverlar, CDN'lar): qaysi biri birinchi muvaffaqiyatli bersa, o'sha.

Tekshirib ko'ring: Ro'yxatdan o'tishda ikki tekshiruv parallel bajariladi: "login band emasmi?" va "email band emasmi?". Ikkalasi ham o'tishi shart. Qaysi kombinator?

Javob

Promise.all. Ikkala natija ham kerak va bittasi muvaffaqiyatsiz bo'lsa, ro'yxatdan o'tish baribir bo'lmaydi — ikkinchisini kutishning ma'nosi yo'q (fail-fast). Agar foydalanuvchiga ikkala xatoni birdan ko'rsatmoqchi bo'lsangiz ("login ham, email ham band") — allSettled.

7. Ko'p uchraydigan xatolar

7.1 Promise o'rniga funksiya berish

js
function tayyorla(taom) {
  return new Promise((resolve) => setTimeout(resolve, 100, taom));
}

async function sinab() {
  const xato = await Promise.all([tayyorla, tayyorla]);
  console.log(xato);
  const togri = await Promise.all([
    tayyorla("osh"),
    tayyorla("choy"),
  ]);
  console.log(togri);
}

sinab();

Konsolda:

text
[ [Function: tayyorla], [Function: tayyorla] ]
[ 'osh', 'choy' ]

Birinchi qatorda funksiyalar chaqirilmagan — Promise.all ularni oddiy qiymat deb qabul qildi va xatosiz qaytardi. Hech qanday ish bajarilmadi. Kombinatorlar Promise'larni kutadi, ishni boshlamaydi. Ishni siz boshlaysiz — tayyorla("osh") chaqiruvi bilan.

7.2 Massivsiz chaqirish

js
const osh = Promise.resolve("osh");
const choy = Promise.resolve("choy");
Promise.all(osh, choy).then((taomlar) => console.log(taomlar));
text
TypeError: object is not iterable (cannot read property Symbol(Symbol.iterator))

"Obyekt iterable emas ('Symbol.iterator' xususiyatini o'qib bo'lmadi)". Kombinator bitta argument kutadi — iterable (odatda massiv). osh Promise'i iterable emas, choy esa umuman e'tiborsiz qoldi. Xabardagi Symbol.iterator — iteratsiya protokoli dagi kalit: JavaScript argumentni aylanib chiqmoqchi bo'ldi. Tuzatish — kvadrat qavslar: Promise.all([osh, choy]).

7.3 allSettled natijasini tekshirmaslik

allSettled rad etilmagani uchun "hammasi joyida" deb o'ylash oson. natijalar.map((n) => n.value) yozsangiz, rad etilganlar o'rnida undefined chiqadi — xato jim yo'qoladi. Har doim status ga qarang.

7.4 race ni "bekor qilish" deb o'ylash

race yoki all ning rad etilishi yutqazgan ishlarni to'xtatmaydi — buni bugun uch marta ko'rdik. Oshxona ortiqcha osh pishiradi, server ortiqcha so'rovni bajaradi. Haqiqiy to'xtatish — AbortController.

7.5 any va race ni adashtirish

Ikkalasi ham "birinchi"ni oladi. Farqi — xatoga munosabatda. Zaxira manbalar uchun race ishlatilsa, birinchi bo'lib tez rad etilgan manba hammasini buzadi. Qoida: "xato ham javob" — race, "menga natija kerak" — any.

8. Mashqlar

1-mashq (oson): Natijani oldindan ayting

Uchta Promise bor: a — 300 ms da "osh" bilan bajariladi, b — 100 ms da "Choy tugadi" xatosi bilan rad etiladi, c — 200 ms da "somsa" bilan bajariladi. Har kombinator nima beradi?

  • Promise.all([a, b, c]) — rad etiladi, xabari:
  • Promise.race([a, b, c]) — rad etiladi, xabari:
  • Promise.any([a, b, c]) — bajariladi, qiymati:
  • Promise.allSettled([a, b, c]) — ikkinchi hisobotning status i:
Yechim
  • all — birinchi rad etilish 100 ms da: "Choy tugadi". Fail-fast.
  • race — birinchi hal bo'lgan ham b: u rad etilgan, demak race ham "Choy tugadi" bilan rad etiladi.
  • any — rad etilgan b o'tkazib yuboriladi, birinchi bajarilgan — c (200 ms): "somsa".
  • allSettled — hisobotlar berilgan tartibda: fulfilled, rejected, fulfilled. Ikkinchisi — rejected.

Tekshirish uchun kod:

js
function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      xato ? reject(new Error(xato)) : resolve(taom);
    }, ms);
  });
}

function uchta() {
  return [
    tayyorla("osh", 300),
    tayyorla("choy", 100, "Choy tugadi"),
    tayyorla("somsa", 200),
  ];
}

async function tekshir() {
  await Promise.all(uchta())
    .catch((x) => console.log("all:", x.message));
  await Promise.race(uchta())
    .catch((x) => console.log("race:", x.message));
  console.log("any:", await Promise.any(uchta()));
  const hisobot = await Promise.allSettled(uchta());
  console.log("allSettled:", hisobot[1].status);
}

tekshir();

Konsolda:

text
all: Choy tugadi
race: Choy tugadi
any: somsa
allSettled: rejected

uchta() har safar yangi Promise'lar yaratadi. Bitta massivni to'rt marta bersak, ular allaqachon hal bo'lgan bo'lardi va vaqtlar tekshirilmasdi. xato ? ... : ... — ternary operator.

2-mashq (o'rta): Menyuni zaxira bilan olish

«Bahor» menyusini uch manbadan olish mumkin: asosiy server (sekin, ba'zan ishlamaydi), zaxira server va keshdagi nusxa. menyuniOl(manbalar) funksiyasini yozing:

  • birinchi muvaffaqiyatli manbaning menyusini qaytarsin;
  • hamma manba xato bersa — "Menyu hozircha mavjud emas" matnini qaytarsin va har xatoni console.log bilan chiqarsin.

Manbani manba(nom, ms, menyu) bilan taqlid qiling: menyu berilmasa — ${nom} ishlamayapti xatosi bilan rad etilsin. Ishora: Promise.any va catch ichida xato.errors.

Yechim
js
function manba(nom, ms, menyu) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (menyu) {
        resolve(`${nom}: ${menyu.join(", ")}`);
      } else {
        reject(new Error(`${nom} ishlamayapti`));
      }
    }, ms);
  });
}

async function menyuniOl(manbalar) {
  try {
    return await Promise.any(manbalar);
  } catch (xato) {
    for (const x of xato.errors) {
      console.log(" -", x.message);
    }
    return "Menyu hozircha mavjud emas";
  }
}

async function sinab() {
  console.log(await menyuniOl([
    manba("asosiy server", 300),
    manba("zaxira server", 200, ["osh", "manti"]),
    manba("kesh", 50, ["osh"]),
  ]));
  console.log(await menyuniOl([
    manba("asosiy server", 100),
    manba("zaxira server", 150),
  ]));
}

sinab();

Konsolda:

text
kesh: osh
 - asosiy server ishlamayapti
 - zaxira server ishlamayapti
Menyu hozircha mavjud emas

Birinchi chaqiruvda kesh eng tez (50 ms) va muvaffaqiyatli — any uni oldi. Ikkinchisida ikkala manba ham rad etildi — AggregateError ning errors massivi ikkala sababni ham saqladi.

return await — try ichida muhim: await siz return Promise.any(...) yozilsa, rad etilish try/catch dan o'tib ketadi va catch ishlamaydi. Bu — async / await darsidagi «return va return await farqi».

3-mashq (qiyin): O'z Promise.all ingiz

Kombinator ichida sehr yo'q — Callbacklar darsidagi qoldi hisoblagichi g'oyasi. hammasi(promiselar) funksiyasini yozing — Promise.all ning soddalashtirilgan nusxasi:

  • new Promise qaytarsin;
  • natijalar berilgan tartibda bo'lsin (natijalar[i] = qiymat);
  • birinchi xatoda — reject;
  • bo'sh massiv — darhol [];
  • Promise bo'lmagan qiymat ham ishlasin — uni Promise.resolve(qiymat) bilan o'rang («Chegaraviy holatlar» bo'limida ko'rdik). Promise bersangiz, u o'shaning o'zini qaytaradi — shuning uchun hamma elementni bemalol o'rash mumkin.

kurs/mashqlar/11/09-kombinatorlar/hammasi.mjs ga saqlang va Promise.all bilan bir xil natija berishini tekshiring.

Yechim
js
function hammasi(promiselar) {
  return new Promise((resolve, reject) => {
    const natijalar = [];
    let qoldi = promiselar.length;
    if (qoldi === 0) {
      resolve(natijalar);
      return;
    }
    promiselar.forEach((p, i) => {
      Promise.resolve(p).then((qiymat) => {
        natijalar[i] = qiymat;
        qoldi--;
        if (qoldi === 0) {
          resolve(natijalar);
        }
      }, reject);
    });
  });
}

function tayyorla(taom, ms, xato) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      xato ? reject(new Error(xato)) : resolve(taom);
    }, ms);
  });
}

async function sinab() {
  console.log(await hammasi([
    tayyorla("osh", 300),
    35000,
    tayyorla("choy", 100),
  ]));
  console.log(await hammasi([]));
  try {
    await hammasi([
      tayyorla("osh", 300),
      tayyorla("choy", 100, "Tugadi"),
    ]);
  } catch (xato) {
    console.log("Rad:", xato.message);
  }
}

sinab();

Konsolda:

text
[ 'osh', 35000, 'choy' ]
[]
Rad: Tugadi

Kalit joylar:

  • natijalar[i] = qiymat — push emas. push kelish tartibida yozardi: choy oshdan oldin.
  • then(qiymat => ..., reject) — then ning ikkinchi argumenti rad etilishda chaqiriladi. Birinchi xato reject ni chaqiradi. Keyingi xatolar ham reject ni chaqiradi, lekin Promise bir marta hal bo'ladi — ular e'tiborsiz qoladi. Callbacklar darsidagi "xato bayrog'i" kerak emas.
  • Haqiqiy Promise.all har qanday iterable'ni qabul qiladi; biznikida forEach bor — faqat massiv. Bu soddalashtirish.
bash
git add 11/09-kombinatorlar/hammasi.mjs
git commit -m "11/09: o'z Promise.all nusxasi"

9. Real ishda

  • Sahifa yuklanishi. Frontend'da sahifa ochilganda bir nechta so'rov parallel yuboriladi va Promise.all bilan kutiladi. React'dagi ma'lumot yuklovchi kutubxonalar (TanStack Query va boshqalar) ichida ham shu g'oya bor — React va ularni kursning keyingi qismlarida o'rganamiz, hozir bilish shart emas.
  • Ommaviy amallar. Yuzlab foydalanuvchiga xabar yuborish, fayllarni qayta ishlash — allSettled va keyin "nechta muvaffaqiyatli, nechta xato" hisoboti. Bir mijozning xatosi qolganlarni to'xtatmasligi kerak.
  • Vaqt chegarasi. Har tashqi so'rovga muddat qo'yish — production'ning qat'iy qoidasi. race bilan qo'lda yoki AbortSignal.timeout bilan.
  • Parallellik cheklovi. Promise.all ga 10 000 ta so'rovni birdan berish serverni "bo'g'ib" qo'yadi. Bir vaqtda faqat 5 tadan bajarish naqshini Asinxron naqshlar darsida yozamiz.
  • Intervyu. "Promise.all va allSettled farqi?", "race va any farqi?", "Promise.all ni o'zingiz yozing" — eng ko'p so'raladigan asinxron savollar. 3-mashq aynan shu.

Xulosa

  • Kombinator bir nechta Promise'dan bitta Promise yasaydi; natija massivlari doim berilgan tartibda.
  • Promise.all — hammasi kerak; birinchi xatoda darhol rad etiladi (fail-fast).
  • Promise.allSettled — har birining taqdiri { status, value } yoki { status, reason }; hech qachon rad etilmaydi.
  • Promise.race — birinchi hal bo'lgan, xato bo'lsa ham; asosiy ishi — vaqt chegarasi (taymerni tozalang).
  • Promise.any — birinchi muvaffaqiyatli; hammasi rad etilsa — AggregateError va uning errors massivi.
  • Hech bir kombinator yutqazgan ishlarni to'xtatmaydi — u faqat kutishni to'xtatadi.

Keyingi dars: Promise yordamchilari va promisify — tayyor Promise yasash, Promise'ni tashqaridan hal qilish (Promise.withResolvers), Promise.try va callback'li funksiyalarni Promise'ga o'rash.

Manbalar

  • ECMAScript spetsifikatsiyasi: "Promise.all", "Promise.allSettled", "Promise.race", "Promise.any", "AggregateError" — tc39.es/ecma262
  • MDN: "Promise.all()", "Promise.allSettled()", "Promise.race()", "Promise.any()", "AggregateError" — developer.mozilla.org
  • web-features (Baseline): promise-allsettled, promise-any — github.com/web-platform-dx/web-features
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Promise kombinatorlari: Promise.all, allSettled, race va any — IlmHamroh