Async/Await — xatolarni ushlash, parallel va ketma-ket bajarish

~20 daq · JavaScript · Asinxron JavaScript

Muammodan boshlaylik

Async/Await ni o'rgandik. Lekin amalda ikki savol doimo chiqadi.

Birinchi savol: Bir funksiyada bir nechta await bor. Har biriga alohida try/catch yozishim kerakmi? Yoki bitta yetarliMi?

Ikkinchi savol: Uchta await ketma-ket yozilgan. Lekin ular bir-biriga bog'liq emas — birining natijasi ikkinchisiga kerak emas. Nima farq qiladi?

Bu ikki savolga aniq javob bilmasdan — async kod yo sekin ishlaydi, yo xatolar noto'g'ri boshqariladi.


Ketma-ket va parallel — farq nima?

Ikkita vazifa bor: foydalanuvchi ma'lumotini olish (1 soniya) va mahsulotlar ro'yxatini olish (1 soniya).

Ketma-ket — biri tugamaguncha keyingisi boshlanmaydi

async function yukla() {
  const foydalanuvchi = await getUser();     // ← 1 soniya kutiladi
  const mahsulotlar   = await getMahsulot(); // ← yana 1 soniya kutiladi

  render(foydalanuvchi, mahsulotlar);
}
// Jami: 2 soniya

Parallel — bir vaqtda ikkalasi boshlanadi

async function yukla() {
  const [foydalanuvchi, mahsulotlar] = await Promise.all([
    getUser(),
    getMahsulot(),
  ]);

  render(foydalanuvchi, mahsulotlar);
}
// Jami: 1 soniya (ikkalasi bir vaqtda ishladi)

Hayotiy o'xshatma

Ertalab nonushta tayyorlamoqdasiz. Choy damlash kerak — 3 daqiqa. Tuxum qovurish kerak — 3 daqiqa.

Ketma-ket: avval choy, keyin tuxum — 6 daqiqa. Parallel: choyni qo'yib, tuxumni ham boshlash — 3 daqiqa.

Agar biri ikkinchisining natijasiga bog'liq bo'lmasa — parallel qilish kerak.


Qachon ketma-ket, qachon parallel?

Ketma-ket — keyingi qadam oldingi natijaga bog'liq bo'lganda:

async function buyurtmaYarat(userId, mahsulotId) {
  const user = await getUser(userId);
  // user.faolmi tekshiriladi — user kerak

  if (!user.faol) throw new Error("Foydalanuvchi faol emas");

  const mahsulot = await getMahsulot(mahsulotId);
  // mahsulot.ombori tekshiriladi — mahsulot kerak

  if (mahsulot.ombor === 0) throw new Error("Omborda yo'q");

  const buyurtma = await yaratBuyurtma(user.id, mahsulot.id);
  // buyurtma yaratildi — ikkalasi ham kerak edi
  return buyurtma;
}

Bu yerda ketma-ket to'g'ri — har qadam oldingi natijaga bog'liq.

Parallel — bir-biriga bog'liq bo'lmaganda:

// ❌ Ketma-ket yozilgan, lekin bog'liq emas — sekin
async function sahifahYukla(userId) {
  const user    = await getUser(userId);
  const postlar = await getPostlar(userId);  // ← user ga bog'liq emas!
  const notiflar = await getNotiflar(userId); // ← user ga bog'liq emas!
  render(user, postlar, notiflar);
}
// Jami: 300ms + 200ms + 400ms = 900ms

// ✅ Parallel — 2x tez
async function sahifahYukla(userId) {
  const [user, postlar, notiflar] = await Promise.all([
    getUser(userId),
    getPostlar(userId),
    getNotiflar(userId),
  ]);
  render(user, postlar, notiflar);
}
// Jami: max(300, 200, 400) = 400ms

Qoida oddiy: "bu so'rovning natijasi boshqa so'rovga kerakmi?" — kerak bo'lsa ketma-ket, kerak bo'lmasa parallel.


Xatolarni ushlash — qanday qilish kerak?

Bitta try/catch — oddiy holat

Ko'p hollarda bitta try/catch yetarli:

async function yukla(id) {
  try {
    const user   = await getUser(id);
    const postlar = await getPostlar(user.id);
    render(user, postlar);
  } catch (xato) {
    // Ikkala so'rovdan ham xato shu yerga tushadi
    xatoBoshqar(xato);
  }
}

Bitta try/catch — istalgan qatordagi xatoni ushlaydi. Bu — eng oddiy va ko'p ishlatiladigan usul.

Alohida try/catch — har xil xatolarni boshqarish

Ba'zan har bir so'rovning xatosi har xil tarzda hal qilinishi kerak:

async function yukla(id) {
  let user;
  try {
    user = await getUser(id);
  } catch (xato) {
    // Foydalanuvchi topilmasa — boshqa sahifaga o'tkazish
    window.location = "/login";
    return;
  }

  let postlar;
  try {
    postlar = await getPostlar(user.id);
  } catch (xato) {
    // Postlar yuklanmasa — bo'sh ro'yxat ko'rsatish, davom etish
    postlar = [];
  }

  render(user, postlar);
}

.catch() bilan standart qiymat berish

Eng ixcham usul — har bir await yoniga .catch() qo'shish:

async function yukla(id) {
  const user    = await getUser(id).catch(() => null);
  const postlar = await getPostlar(id).catch(() => []);

  if (!user) {
    showError("Foydalanuvchi topilmadi");
    return;
  }

  render(user, postlar); // postlar bo'sh bo'lsa ham ishlaydi
}

Loop da parallel so'rovlar

Massiv elementlari uchun parallel so'rovlar — Promise.all va map birga:

const idlar = [1, 2, 3, 4, 5];

// ❌ Ketma-ket — jami: 1+1+1+1+1 = 5 soniya
for (const id of idlar) {
  const user = await getUser(id); // biri tugamaguncha keyingisi boshlanmaydi
  console.log(user);
}

// ✅ Parallel — jami: ~1 soniya (hammasi bir vaqtda)
const users = await Promise.all(idlar.map(id => getUser(id)));
console.log(users);

forEach bilan async — nega ishlamaydi?

forEach — callback bilan chaqiradi, lekin u qaytargan Promise ni kutmaydi. Bu oddiy JavaScript ning dizayn qarori: forEach asinxron bo'lganda nima qilishni bilmaydi.

// ❌ forEach — natija tartibsiz, "tugadi" darhol chiqadi
[1, 2, 3].forEach(async (id) => {
  const user = await getUser(id);
  console.log(user);
});
console.log("tugadi"); // ← loop hali tugamagan!

// ✅ for...of — tartibli, kutadi
for (const id of [1, 2, 3]) {
  const user = await getUser(id);
  console.log(user);
}
console.log("tugadi"); // ← haqiqatan oxirida

Tez-tez uchraydigan xatolar

Parallel bo'lishi mumkin so'rovlarni ketma-ket yozish:

Bu eng ko'p uchraydigan xato — kod to'g'ri ishlaydi, lekin keraksiz sekin.

// ❌ — bog'liq emas, lekin ketma-ket
const a = await getSomething();
const b = await getSomethingElse(); // a ga bog'liq emas, lekin a tugaguncha kutadi

// ✅
const [a, b] = await Promise.all([getSomething(), getSomethingElse()]);

Promise.all da xatoni ko'rmaslik:

// ❌ — biri xato bersa hammasi yo'qoladi, catch yo'q
const [a, b] = await Promise.all([getA(), getB()]);

// ✅
const [a, b] = await Promise.all([
  getA().catch(() => null),
  getB().catch(() => null),
]);

Xulosa

Ketma-ket — await birin-ketin. Har qadam oldingi natijaga bog'liq bo'lganda.

Parallel — await Promise.all([...]). Bir-biriga bog'liq bo'lmagan so'rovlar — doima parallel. Sekin kodning eng keng tarqalgan sababi — parallel bo'lishi mumkin narsani ketma-ket yozish.

Xato ushlash — bitta try/catch oddiy holat uchun. Har xil xatolar uchun — alohida try/catch yoki .catch() bilan standart qiymat.

forEach + async — ishlamaydi. for...of yoki Promise.all([].map(...)) ishlatiladi.


Keyingi dars

41-dars: Fetch API — HTTP so'rovlar, headers, method, body