Mundarija (32)
- Bu darsda
- 1. Nega bu kerak?
- 2. Birinchi o'ram: sorovYubor
- 2.1 Asosiy URL va javob.ok
- 2.2 Ma'lumot yuborish
- 3. Xatolarni bitta shaklga keltirish
- 3.1 Besh xil muvaffaqiyatsizlik
- 3.2 ApiXatosi va to'liq sorovYubor
- 4. Timeout va bekor qilish birga
- 5. Yuklanmoqda, xato, ma'lumot
- 5.1 Uch holat
- 5.2 "Yuklanmoqda" qachon ko'rinsin
- 6. Race condition: eski javob yangisini bosib ketadi
- 6.1 Muammo
- 6.2 Yechim 1: eskisini bekor qilish
- 6.3 Yechim 2: so'rov raqami
- 7. Klient obyekti: apiKlient
- 8. Hujumchi nigohi
- 9. Ko'p uchraydigan xatolar
- 9.1 javob.json() ni javob.ok dan oldin, try siz
- 9.2 Asosiy URL'dagi qiya chiziq
- 9.3 Bekor qilishni xato deb ko'rsatish
- 9.4 finally siz yuklanmoqda
- 9.5 Qatlamni "teshish"
- 10. Mashqlar
- 1-mashq (oson): menyuniOl
- 2-mashq (o'rta): Faqat kerakli xatoda qayta urinish
- 3-mashq (qiyin): Maydon xatolari
- 4-mashq: Vazifalar qadami — api.js
- 11. Real ishda
- Xulosa
- Manbalar
API klient qatlami: hamma fetch larni bitta joyga yig'ish
Qisqacha: API klient qatlami — ilovadagi hamma so'rovlar o'tadigan bitta modul (
api.js). U asosiy URL, sarlavhalar,javob.oktekshiruvi va timeout'ni bir joyda bajaradi. Har xil xatolarni — tarmoq, timeout, 404, 500, buzuq JSON — bittaApiXatosishakliga (holat,kod) keltiradi. Interfeys esafetchni umuman ko'rmaydi:await api.qosh("Non olish")deydi, xolos.
Bu darsda
- Tarqoq
fetchlar nega xavfli ekanini va o'ram (wrapper) qanday yordam berishini tushuntira olasiz. sorovYuborfunksiyasini yozasiz: asosiy URL, JSON,javob.ok, timeout.- Besh xil muvaffaqiyatsizlikni bitta
ApiXatosi(holat,kod) ga keltirasiz. - Interfeysda "yuklanmoqda / xato / ma'lumot" holatlarini to'g'ri ko'rsatasiz.
- Eski javob yangisining ustidan yozib yuboradigan race condition dan himoyalanasiz.
vazifalaruchunapi.jsni yaratasiz.
Oldin bilishingiz kerak: fetch asoslari, fetch bilan ma'lumot yuborish, CORS mijoz tomondan, Asinxron naqshlar, Bekor qilish: AbortController, O'z xato klasslaringiz.
1. Nega bu kerak?
«Bahor»da mehmon oshxonaga o'zi kirmaydi. U ofitsiant Otabekka aytadi: "Bitta osh". Otabek esa hamma narsani biladi: buyurtma qaysi oynaga beriladi, osh tugagan bo'lsa nima deyiladi, oshxona javob bermasa qancha kutiladi. Mehmon uchun bu tafsilotlar yo'q — u faqat "osh keldi" yoki "osh tugadi, lag'mon bor" eshitadi.
Endi vazifalar ilovasini serverga ulayotganimizni tasavvur qiling. Vazifa qo'shish, belgilash, o'chirish, ro'yxatni olish — to'rtta joyda fetch. Har birida bir xil ishlar takrorlanadi:
// ❌ Har tinglovchida o'z fetch'i
const javob = await fetch(
"https://ilmhamroh.uz/api/mashq/vazifalar/" + kalit,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ matn }),
},
);
if (!javob.ok) {
elonQil("Xato!");
return;
}
const vazifa = await javob.json();Bir oydan keyin nima bo'ladi?
- Bitta joyda
javob.oktekshiruvi unutilgan. Server 500 qaytaradi, ilova esa xato matnini "vazifa" deb chizadi. - Server manzili o'zgardi — 12 ta faylni qidirib, 12 joyni tuzatasiz. Bittasi qolib ketadi.
- Bir joyda xato "Xato!", boshqasida "Error", uchinchisida umuman jim. Foydalanuvchi nima bo'lganini tushunmaydi.
- Timeout hech qayerda yo'q. Sekin internetda tugma abadiy "kutilmoqda".
Yechim — API klient qatlami (API client layer): server bilan gaplashadigan yagona modul. Ilovaning qolgan qismi uchun u Otabek: api.qosh("Non olish") deydi va yo vazifa oladi, yo tushunarli xato. fetch, URL, sarlavha, timeout — hammasi shu modul ichida.
flowchart TD
UI["Interfeys<br/>render.js"] -->|"api.qosh()"| K["API klient<br/>api.js"]
K --> S["sorovYubor<br/>URL, JSON, timeout"]
S -->|fetch| SV[("Server<br/>/api/mashq")]
SV -->|javob| S
S -->|"ma'lumot yoki ApiXatosi"| K
K --> UISxemaga qarang: interfeys faqat yuqoridagi strelkani biladi. fetch — faqat pastda, bitta joyda.
2. Birinchi o'ram: sorovYubor
2.1 Asosiy URL va javob.ok
O'ram (wrapper) — boshqa funksiyani ichida chaqirib, unga qo'shimcha ish qo'shadigan funksiya. Bizniki fetch ni o'raydi.
Birinchi qadam: takrorlanadigan uch narsani bitta joyga olamiz — asosiy URL, JSON o'qish va javob.ok tekshiruvi.
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
async function sorovYubor(yol) {
const javob = await fetch(ASOSIY_URL + yol);
const malumot = await javob.json();
if (!javob.ok) {
throw new Error(malumot.xato);
}
return malumot;
}
const osh = await sorovYubor("/menyu/1");
console.log(osh.nom, osh.narx); // Osh 35000
try {
await sorovYubor("/menyu/99");
} catch (xato) {
console.log("Xato:", xato.message); // Xato: Taom topilmadi
}Asosiy URL (base URL) — hamma so'rovlar uchun umumiy boshlanish: https://ilmhamroh.uz/api/mashq. Har chaqiruv faqat yo'l (path) beradi: /menyu/1. Server boshqa manzilga ko'chsa — bitta qatorni o'zgartirasiz.
Endi sorovYubor 404 da xato tashlaydi — fetch ning o'zi buni qilmasdi (fetch asoslari). Chaqiruvchi try/catch bilan ushlaydi. Muvaffaqiyatli javob esa to'g'ridan-to'g'ri ma'lumot: osh.nom.
2.2 Ma'lumot yuborish
Yozish so'rovlari (fetch bilan ma'lumot yuborish) uchun metod va tana kerak. JSON.stringify va Content-Type ni ham o'ram o'z zimmasiga oladi:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
async function sorovYubor(yol, { metod = "GET", tana } = {}) {
const sozlama = { method: metod, headers: {} };
if (tana !== undefined) {
sozlama.headers["Content-Type"] = "application/json";
sozlama.body = JSON.stringify(tana);
}
const javob = await fetch(ASOSIY_URL + yol, sozlama);
const malumot = await javob.json();
if (!javob.ok) {
throw new Error(malumot.xato);
}
return malumot;
}
const aks = await sorovYubor("/aks", {
metod: "POST",
tana: { taom: "Manti", soni: 2 },
});
console.log(aks.metod, aks.sarlavhalar["content-type"]);
console.log(aks.tana);Konsolda:
POST application/json
{ taom: 'Manti', soni: 2 }/aks — mashq API'ning "oynasi": u kelgan so'rovni qaytaradi. Ko'ryapsizmi, metod, sarlavha va tana to'g'ri ketdi.
Ikkinchi parametr — sozlama obyekti (Parametrda destructuring va options obyekti). = {} — sozlama berilmasa ham destructuring xato bermasin. metod = "GET" — standart qiymat.
Tekshirib ko'ring:
sorovYubor("/menyu")chaqirilgandasozlama.bodybormi?Content-Typechi?
Javob
Ikkalasi ham yo'q. tana berilmadi, ya'ni undefined — if ichi bajarilmaydi. GET so'rovning tanasi bo'lmaydi, shuning uchun Content-Type ham kerak emas.
3. Xatolarni bitta shaklga keltirish
3.1 Besh xil muvaffaqiyatsizlik
Hozirgi sorovYubor faqat bitta holatni yaxshi biladi: server JSON xato qaytardi. Real hayotda esa so'rov besh xil yo'l bilan muvaffaqiyatsiz bo'ladi:
| Nima bo'ldi | JavaScript'da nima keladi | Bizning kod |
|---|---|---|
| Internet yo'q, server topilmadi | TypeError: fetch failed (Chrome: Failed to fetch) |
TARMOQ |
| Server juda uzoq javob bermadi | TimeoutError |
VAQT |
| So'rovni o'zimiz bekor qildik | AbortError |
BEKOR |
| Server 4xx/5xx qaytardi | javob.ok === false |
TOPILMADI, NOTOGRI, SERVER, HTTP |
| Javob JSON emas (HTML xato sahifasi) | SyntaxError |
JAVOB |
Har birining shakli har xil: biri TypeError, biri DOMException (TimeoutError va AbortError shu turdagi xatolar), biri umuman xato emas — oddiy javob. Interfeys bularni alohida-alohida tanishi kerakmi? Yo'q. Xato normalizatsiyasi (normalization) — har xil xatolarni bitta shaklga keltirish. Bizda u shakl — ApiXatosi:
holat— HTTP holat kodi (404, 500); server javob bermagan bo'lsa0.kod— qisqa, dasturchi uchun nom:TARMOQ,VAQT,TOPILMADI...message— foydalanuvchiga ko'rsatsa bo'ladigan o'zbekcha matn.
Bu O'z xato klasslaringiz darsidagi g'oya: vazifalar dagi VazifaXatosi ham kod bilan edi.
3.2 ApiXatosi va to'liq sorovYubor
Endi besh holatni ham ushlaydigan versiya. Kod uzun, shuning uchun avval uning yo'lini sxemada ko'raylik. Javob yuqoridan pastga uchta "elak" dan o'tadi. Qaysi elakda to'xtasa — o'sha kod chiqadi:
flowchart TD
F["fetch + text()"] -->|"xato tashladi"| T["tarmoqXatosi:<br/>VAQT / BEKOR / TARMOQ"]
F -->|"matn keldi"| J["JSON.parse"]
J -->|"buzuq JSON"| JA["JAVOB"]
J -->|"o'qildi"| O{"javob.ok?"}
O -->|"yo'q"| H["holatKodi:<br/>TOPILMADI / NOTOGRI /<br/>SERVER / HTTP"]
O -->|"ha"| M["ma'lumot qaytadi"]Endi kodning o'zi. Pastdagi besh sinov har bir holatni ko'rsatadi:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
class ApiXatosi extends Error {
constructor(xabar, sozlama) {
super(xabar, sozlama);
this.name = "ApiXatosi";
this.holat = sozlama?.holat ?? 0;
this.kod = sozlama?.kod ?? "NOMALUM";
}
}
function holatKodi(holat) {
if (holat === 404) return "TOPILMADI";
if (holat === 400 || holat === 422) return "NOTOGRI";
if (holat >= 500) return "SERVER";
return "HTTP";
}
function tarmoqXatosi(xato) {
if (xato.name === "TimeoutError") {
return new ApiXatosi("Server javob bermadi.", {
kod: "VAQT", cause: xato,
});
}
if (xato.name === "AbortError") {
return new ApiXatosi("So'rov bekor qilindi.", {
kod: "BEKOR", cause: xato,
});
}
return new ApiXatosi("Internet aloqasini tekshiring.", {
kod: "TARMOQ", cause: xato,
});
}
async function sorovYubor(yol, { kutish = 8000 } = {}) {
let javob;
let matn;
try {
javob = await fetch(ASOSIY_URL + yol, {
signal: AbortSignal.timeout(kutish),
});
matn = await javob.text();
} catch (xato) {
throw tarmoqXatosi(xato);
}
let malumot = null;
if (matn !== "") {
try {
malumot = JSON.parse(matn);
} catch (xato) {
throw new ApiXatosi("Server JSON qaytarmadi.", {
holat: javob.status, kod: "JAVOB", cause: xato,
});
}
}
if (!javob.ok) {
const xabar = malumot?.xato ?? `Server xatosi: ${javob.status}`;
throw new ApiXatosi(xabar, {
holat: javob.status, kod: holatKodi(javob.status),
});
}
return malumot;
}
const sinovlar = [
["/menyu/1", {}],
["/menyu/99", {}],
["/holat/503", {}],
["/oqim?bolaklar=1", {}],
["/menyu?kechikish=3000", { kutish: 1000 }],
];
for (const [yol, sozlama] of sinovlar) {
try {
const taom = await sorovYubor(yol, sozlama);
console.log("✅", taom.nom);
} catch (xato) {
console.log(`❌ ${xato.kod} (${xato.holat}): ${xato.message}`);
}
}Konsolda:
✅ Osh
❌ TOPILMADI (404): Taom topilmadi
❌ SERVER (503): Server xatosi: 503
❌ JAVOB (200): Server JSON qaytarmadi.
❌ VAQT (0): Server javob bermadi.Qismma-qism:
tryichidafetchvatext(). Ikkalasi ham tarmoq bilan ishlaydi: ulanish uzilishi yoki timeout ikkalasining o'rtasida ham bo'lishi mumkin.catchularnitarmoqXatosiga beradi — unamega qarabVAQT,BEKORyokiTARMOQyasaydi.json()emas,text()+JSON.parse. Shunda "javob keldi, lekin JSON emas" holatini alohida ushlaymiz./oqimoddiy matn qaytaradi — sinovda shuJAVOBbo'ldi. Real hayotda bu ko'pincha HTML xato sahifasi bo'ladi. Masalan, nginx ilova ishlamay qolsa "502 Bad Gateway" degan HTML qaytaradi. Nginx — server oldida turib, so'rovlarni ilovaga uzatadigan dastur; uni backend qismlarida o'rnatamiz.- Bo'sh tana.
204 No Contentjavobida tana yo'q:matn === "". Undamalumot—null, xato emas. malumot?.xato ?? ...— server xabari bo'lsa, o'shani olamiz./holat/503ning tanasidaxatomaydoni yo'q (u{holat, xabar}qaytaradi). Shunda zaxira matn ishladi:Server xatosi: 503.cause— asl xato yo'qolmaydi. Konsolda faqat bizning matn, lekin xatoni tekshirayotgan dasturchixato.causedan aslTypeErrorni ko'radi.
Endi interfeys kodi faqat bitta narsani biladi: catch (xato) ichida xato.kod va xato.message bor. Qaysi kodda nima qilishni o'zi hal qiladi: TARMOQ — "Internetni tekshiring" va «Qayta urinish» tugmasi, NOTOGRI — maydon yonida xabar, BEKOR — umuman hech narsa ko'rsatmaslik.
Tekshirib ko'ring: Server
401qaytarsa,holatKodiqandaykodberadi?xato.holatchi?
Javob
kod — HTTP (401 ro'yxatdagi uchta shartga ham tushmaydi), holat — 401. Ilovaga kirish (login) kerak bo'lganda 401 ni alohida kod bilan ajratish foydali — buni Autentifikatsiyali so'rovlar darsida qilamiz.
4. Timeout va bekor qilish birga
fetch ning o'zida timeout yo'q. Server javob bermasa, brauzer bir necha daqiqa kutishi mumkin. Shuning uchun har so'rovga AbortSignal.timeout(ms) beramiz — Bekor qilish: AbortController darsidan tanish.
Lekin ba'zan chaqiruvchi ham o'z signalini bermoqchi: "foydalanuvchi sahifadan chiqib ketdi — so'rovni to'xtat". fetch esa faqat bitta signal oladi. Ikki signalni birlashtirish — AbortSignal.any([a, b]): ulardan qaysi biri birinchi to'xtasa, natija ham to'xtaydi.
const vaqt = AbortSignal.timeout(kutish);
const signal = tashqiSignal
? AbortSignal.any([tashqiSignal, vaqt])
: vaqt;Shu bilan bitta so'rov ikki sababdan to'xtashi mumkin. tarmoqXatosi ularni farqlaydi: timeout — TimeoutError → VAQT, qo'lda abort() — AbortError → BEKOR. Bu farq muhim: timeout — muammo, foydalanuvchiga aytish kerak. Bekor qilish esa ataylab qilingan — xato xabari kerak emas.
kutish qancha bo'lsin? Ko'p loyihalarda 8–15 soniya. Juda qisqa bo'lsa, sekin mobil internetda (Aziz kabi foydalanuvchilarda) hamma so'rov VAQT bo'ladi. Juda uzun bo'lsa — foydalanuvchi tugma abadiy aylanayotganini ko'radi.
5. Yuklanmoqda, xato, ma'lumot
5.1 Uch holat
Har so'rovning interfeysdagi hayoti bir xil:
stateDiagram-v2
[*] --> Bosh
Bosh --> Yuklanmoqda: so'rov
Yuklanmoqda --> Tayyor: ma'lumot keldi
Yuklanmoqda --> Xato: ApiXatosi
Xato --> Yuklanmoqda: Qayta urinish
Tayyor --> Yuklanmoqda: yangilashUch holatni bitta obyektda saqlaymiz va ekranni faqat render() chizadi — Holatdan UI chizish: state → render darsidagi qoida:
yuklanmoqda: true— tugma o'chiriladi (ikki marta bosilmasin), "Yuklanmoqda..." ko'rsatiladi.xato— xabar va «Qayta urinish» tugmasi.taomlar— ro'yxat.
<style>
body { font: 1rem/1.5 system-ui, sans-serif; margin: 1rem; }
button { font: inherit; margin: 0 0.25rem 0.5rem 0; }
.xato { color: #b3261e; }
</style>
<button id="menyu" type="button">Menyu</button>
<button id="yoq" type="button">Yo'q taom (404)</button>
<p id="holat" role="status"></p>
<ul id="royxat"></ul>
<script>
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
const holat = { yuklanmoqda: false, xato: null, taomlar: [] };
const holatEl = document.getElementById("holat");
const royxatEl = document.getElementById("royxat");
const tugmalar = document.querySelectorAll("button");
function render() {
for (const tugma of tugmalar) tugma.disabled = holat.yuklanmoqda;
holatEl.className = holat.xato ? "xato" : "";
if (holat.yuklanmoqda) holatEl.textContent = "Yuklanmoqda...";
else if (holat.xato) holatEl.textContent = holat.xato;
else holatEl.textContent = `${holat.taomlar.length} ta taom`;
royxatEl.replaceChildren(...holat.taomlar.map((t) => {
const li = document.createElement("li");
li.textContent = `${t.nom} — ${t.narx} so'm`;
return li;
}));
}
async function yukla(yol) {
holat.yuklanmoqda = true;
holat.xato = null;
render();
console.log("holat: yuklanmoqda");
try {
const javob = await fetch(ASOSIY_URL + yol);
const malumot = await javob.json();
if (!javob.ok) throw new Error(malumot.xato);
holat.taomlar = Array.isArray(malumot) ? malumot : [malumot];
console.log("holat: tayyor,", holat.taomlar.length, "ta");
} catch (xato) {
holat.xato = `Xato: ${xato.message}`;
holat.taomlar = [];
console.log("holat: xato —", xato.message);
} finally {
holat.yuklanmoqda = false;
render();
}
}
document.getElementById("menyu").addEventListener("click", () => {
yukla("/menyu?kechikish=1200");
});
document.getElementById("yoq").addEventListener("click", () => {
yukla("/menyu/99?kechikish=800");
});
yukla("/menyu?kechikish=1200");
</script>Konsolda:
holat: yuklanmoqda
holat: tayyor, 4 taSahifa ochilganda 1,2 soniya "Yuklanmoqda..." va o'chirilgan tugmalar ko'rinadi. ?kechikish=1200 — server ataylab kechikadi, sekin internetni sinash uchun. Keyin «Yo'q taom (404)» ni bosing: xabar qizil bo'ladi, ro'yxat tozalanadi.
finally muhim: muvaffaqiyat ham, xato ham bo'lsa — yuklanmoqda albatta false bo'ladi. Aks holda xatodan keyin tugmalar abadiy o'chiq qolardi.
role="status" — ekran o'quvchi holat o'zgarishini o'qib beradi. Server xabari esa textContent bilan chiqdi — innerHTML emas (sababini «Hujumchi nigohi» da ko'ramiz).
5.2 "Yuklanmoqda" qachon ko'rinsin
Kichik, lekin foydali tafsilot: javob 100 ms da kelsa, "Yuklanmoqda..." bir lahzaga miltillaydi va ko'zni charchatadi. Ko'p ilovalar "spinner" (aylanib turadigan kutish belgisi) ni faqat so'rov 300–500 ms dan uzoq cho'zilsa ko'rsatadi: setTimeout bilan kechiktiriladi va javob kelganda clearTimeout qilinadi (Taymerlar). Bu — 2-mashqning bir qismi bo'lishi mumkin.
6. Race condition: eski javob yangisini bosib ketadi
6.1 Muammo
«Bahor» saytida turkum tugmalari bor: «Taomlar», «Ichimliklar». Mehmon «Taomlar» ni bosdi, keyin fikrini o'zgartirib, darhol «Ichimliklar» ni bosdi. Ekranda ichimliklar bo'lishi kerak.
Lekin internet tekis ishlamaydi: birinchi so'rov sekin keldi (800 ms), ikkinchisi tez (100 ms). Buni ?kechikish= bilan yasaymiz. Ikki tez bosishni esa Promise.all bilan taqlid qilamiz: ikkala chaqiruv deyarli bir vaqtda boshlanadi, xuddi tugmalar ketma-ket bosilgandek. ekranda o'zgaruvchisi — ekranning o'rnida:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
let ekranda = "";
async function turkumniKorsat(turkum, kechikish) {
const url =
`${ASOSIY_URL}/menyu?turkum=${turkum}&kechikish=${kechikish}`;
const javob = await fetch(url);
const taomlar = await javob.json();
ekranda = `${turkum}: ${taomlar.map((t) => t.nom).join(", ")}`;
}
await Promise.all([
turkumniKorsat("taom", 800),
turkumniKorsat("ichimlik", 100),
]);
console.log(ekranda); // taom: Osh, Lag'mon, MantiMehmon oxirida «Ichimliklar» ni tanlagan edi, ekranda esa taomlar. Ichimliklar javobi 100 ms da keldi va chizildi, keyin 800 ms da eski javob keldi va uning ustidan yozdi.
Race condition (poyga holati) — natija ishlarning qaysi tartibda tugashiga bog'liq bo'lib qoladigan xato. Bu xato faqat ba'zan chiqadi — sekin tarmoqda — va shuning uchun xavfli: dasturchi kompyuterida hech qachon ko'rinmaydi. Asinxron naqshlar darsida "oxirgi so'rov yutadi" naqshini ko'rgan edik — endi uni haqiqiy fetch ga qo'llaymiz.
Ikki yechim bor.
6.2 Yechim 1: eskisini bekor qilish
Har yangi so'rov eskisining AbortController ini abort() qiladi:
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
let ekranda = "";
let joriy = null;
async function turkumniKorsat(turkum, kechikish) {
joriy?.abort();
const boshqaruvchi = new AbortController();
joriy = boshqaruvchi;
const url =
`${ASOSIY_URL}/menyu?turkum=${turkum}&kechikish=${kechikish}`;
try {
const javob = await fetch(url, { signal: boshqaruvchi.signal });
const taomlar = await javob.json();
ekranda = `${turkum}: ${taomlar.map((t) => t.nom).join(", ")}`;
} catch (xato) {
if (xato.name === "AbortError") {
console.log(`«${turkum}» bekor qilindi`);
return;
}
throw xato;
}
}
await Promise.all([
turkumniKorsat("taom", 800),
turkumniKorsat("ichimlik", 100),
]);
console.log(ekranda);Konsolda:
«taom» bekor qilindi
ichimlik: Ko'k choyjoriy?.abort() — oldingi so'rov bo'lsa, uni to'xtatadi. ?. — birinchi chaqiruvda joriy hali null. Bekor qilingan so'rov AbortError bilan tugaydi — uni xato deb ko'rsatmaymiz, jim chiqamiz. Bonus: brauzer eski ulanishni uzadi, trafik tejaladi.
6.3 Yechim 2: so'rov raqami
Ba'zan so'rovni bekor qilib bo'lmaydi (masalan, u boshqa kutubxona ichida). Unda har so'rovga raqam beramiz va javob kelganda tekshiramiz: "men hali oxirgisimanmi?"
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
let ekranda = "";
let oxirgiRaqam = 0;
async function turkumniKorsat(turkum, kechikish) {
const raqam = ++oxirgiRaqam;
const url =
`${ASOSIY_URL}/menyu?turkum=${turkum}&kechikish=${kechikish}`;
const javob = await fetch(url);
const taomlar = await javob.json();
if (raqam !== oxirgiRaqam) {
console.log(`«${turkum}» eskirdi — tashlandi`);
return;
}
ekranda = `${turkum}: ${taomlar.map((t) => t.nom).join(", ")}`;
}
await Promise.all([
turkumniKorsat("taom", 800),
turkumniKorsat("ichimlik", 100),
]);
console.log(ekranda);Konsolda:
«taom» eskirdi — tashlandi
ichimlik: Ko'k choy++oxirgiRaqam — avval oshiradi, keyin qiymatni beradi: birinchi so'rov 1, ikkinchisi 2. Taom javobi kelganda oxirgiRaqam allaqachon 2 — demak u eskirgan. Bu yechimda so'rov oxirigacha boradi, faqat natijasi tashlanadi.
Tekshirib ko'ring: «Muammo» bo'limidagi kodda kechikishlarni almashtirsak —
taom100 ms,ichimlik800 ms — xato ko'rinadimi?
Javob
Yo'q: eski javob (taom) birinchi keladi, yangisi (ichimlik) keyin keladi va to'g'ri yozadi. Race condition shuning uchun xavfli — u faqat ma'lum tezliklarda ko'rinadi. Himoya har doim bo'lishi kerak, "bizda ishlayapti" yetarli emas.
7. Klient obyekti: apiKlient
sorovYubor umumiy ishni qiladi. Ilovaning qolgan qismi uchun esa yanada qulayroq — resurs metodlari. Resurs — serverdagi bitta "narsa" turi (bizda — vazifalar ro'yxati). Resurs metodi shu narsa bilan bitta ishni bajaradi: api.royxat() — ro'yxatni olish, api.qosh(matn) — vazifa qo'shish. Ularni Closure amaliyotda dagi fabrika bilan yasaymiz:
export function apiKlient({ asosiyUrl, kalit }) {
const manzil = `${asosiyUrl}/vazifalar/${kalit}`;
return {
royxat: (sozlama) => sorovYubor(manzil, sozlama),
qosh: (matn, sozlama) => sorovYubor(manzil, {
...sozlama, metod: "POST", tana: { matn },
}),
// yangila, ochir ...
};
}Fabrikaning foydasi: asosiyUrl va kalit bir marta beriladi va closure'da eslab qolinadi. Sinov uchun boshqa server, haqiqiy ilova uchun boshqasi — ikkita klient, kod bir xil.
Har metod oxirgi parametr sifatida sozlama oladi va uni ...sozlama bilan sorovYubor ga uzatadi. Shunda chaqiruvchi signal yoki kutish bera oladi: api.royxat({ signal }).
8. Hujumchi nigohi
API qatlami — brauzer va server orasidagi chegara. Bu yerda uch narsa buziladi.
Server matnini HTML qilib chiqarish. holatEl.innerHTML = xato.message — xavfli. Hujumchi so'rov yo'lidagi biror bo'g'inni boshqarsa — o'z serverini yoki o'rtadagi proksini (brauzer va server orasida so'rovni uzatib turuvchi kompyuter) — xato maydoniga <img src=x onerror="..."> qo'yadi. Natijada foydalanuvchi brauzerida o'z kodini ishga tushiradi (XSS — Kontentni o'zgartirish va XSS xavfi). Himoya: server matni — faqat textContent, bizning natijadagi kabi.
Maxfiy kalitni frontend'ga yozish. Ba'zi API'lar "API key" talab qiladi: ob-havo, SMS, to'lov. const KALIT = "sk_live_..." ni api.js ga yozsangiz, uni har bir foydalanuvchi DevTools'ning Sources panelida o'qiydi va sizning hisobingizdan foydalanadi. Himoya: maxfiy kalit faqat serverda. Brauzer o'z serveringizga so'rov yuboradi, u esa kalit bilan tashqi API'ga. Buni backend qismlarida quramiz (Sirlarni boshqarish).
kalit — parol o'rnida. Mashq API'dagi vazifalar/:kalit — ochiq: kalitni bilgan har kim ro'yxatni o'qiydi va o'zgartiradi. Shuning uchun u crypto.randomUUID() — taxmin qilib bo'lmaydigan 122 bitli tasodifiy son. aziz-vazifalari kabi kalitni esa bir daqiqada topishadi. Haqiqiy ilovada kalit o'rnida login va token bo'ladi — Autentifikatsiyali so'rovlar darsida.
9. Ko'p uchraydigan xatolar
9.1 javob.json() ni javob.ok dan oldin, try siz
Server 502 da nginx'ning HTML sahifasini qaytaradi. await javob.json() — SyntaxError: Unexpected token '<', "<html>..." is not valid JSON. Foydalanuvchi tushunarsiz xato ko'radi. Tuzatish: text() + JSON.parse try ichida, JAVOB kodi bilan — yuqoridagi sorovYubor kabi.
9.2 Asosiy URL'dagi qiya chiziq
ASOSIY_URL = ".../api/mashq/" va yol = "/menyu" — natija .../mashq//menyu. Ko'p serverlar buni 404 qiladi. Tuzatish: bitta qoida tanlang va hamma joyda unga amal qiling: asosiy URL chiziqsiz tugaydi, yo'l chiziq bilan boshlanadi. URL'ni xatosiz yig'ish uchun maxsus URL obyekti ham bor — URL va URLSearchParams darsida.
9.3 Bekor qilishni xato deb ko'rsatish
Race condition'dan himoya qo'shdingiz va endi har tez bosishda qizil "So'rov bekor qilindi" chiqyapti. Tuzatish: kod === "BEKOR" bo'lsa, hech narsa ko'rsatmang — bu foydalanuvchining o'z harakati.
9.4 finally siz yuklanmoqda
yuklanmoqda = false ni faqat try oxiriga yozish — xatoda tugma abadiy o'chiq. Tuzatish: finally ga.
9.5 Qatlamni "teshish"
api.js yozildi, lekin shoshilib bitta tinglovchida yana to'g'ridan-to'g'ri fetch yozildi. Endi timeout va xato shakli u yerda yo'q. Tuzatish: qoida — fetch so'zi faqat api.js da uchraydi. VS Code'da Ctrl+Shift+F bilan fetch( ni qidirib tekshirish oson.
10. Mashqlar
1-mashq (oson): menyuniOl
«ApiXatosi va to'liq sorovYubor» bo'limidagi sorovYubor dan foydalanib menyuniOl(turkum) funksiyasini yozing: turkum berilsa — /menyu?turkum=..., berilmasa — /menyu. U taom nomlari massivini qaytarsin. menyuniOl("ichimlik") va menyuniOl() natijasini chiqaring. Kodni qisqa qilish uchun sorovYubor ning faqat javob.ok li oddiy versiyasi («Asosiy URL va javob.ok» bo'limidagi) yetarli.
Yechim
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
async function sorovYubor(yol) {
const javob = await fetch(ASOSIY_URL + yol);
const malumot = await javob.json();
if (!javob.ok) {
throw new Error(malumot.xato);
}
return malumot;
}
async function menyuniOl(turkum) {
const yol = turkum ? `/menyu?turkum=${turkum}` : "/menyu";
const taomlar = await sorovYubor(yol);
return taomlar.map((t) => t.nom);
}
console.log(await menyuniOl("ichimlik"));
console.log(await menyuniOl());Konsolda:
[ "Ko'k choy" ]
[ 'Osh', "Lag'mon", 'Manti', "Ko'k choy" ]Node apostrofli satrni qo'sh qo'shtirnoqda ko'rsatadi: "Ko'k choy". menyuniOl fetch ni ham, URL'ni ham bilmaydi — faqat yo'lni. Bu qatlamning o'zi.
2-mashq (o'rta): Faqat kerakli xatoda qayta urinish
qaytaUrin(ish, marta) funksiyasini yozing (Asinxron naqshlar dagi retry). U ish() ni chaqirsin va ApiXatosi ning kod i TARMOQ, VAQT yoki SERVER bo'lsa, 200 × urinish ms kutib qayta urinsin (ko'pi bilan marta marta). Kod TOPILMADI yoki NOTOGRI bo'lsa, darhol xato tashlasin — ularni qayta so'rash befoyda.
Sinov uchun ish sifatida ApiXatosi ni tashlaydigan soxta funksiya yetarli: birinchi ikki chaqiruvda SERVER, uchinchisida "Osh" qaytarsin. Ikkinchi sinov: har doim TOPILMADI tashlasin.
Yechim
class ApiXatosi extends Error {
constructor(xabar, sozlama) {
super(xabar, sozlama);
this.name = "ApiXatosi";
this.holat = sozlama?.holat ?? 0;
this.kod = sozlama?.kod ?? "NOMALUM";
}
}
const QAYTA_KODLAR = new Set(["TARMOQ", "VAQT", "SERVER"]);
const kut = (ms) => new Promise((tayyor) => setTimeout(tayyor, ms));
async function qaytaUrin(ish, marta) {
for (let urinish = 1; ; urinish++) {
try {
return await ish();
} catch (xato) {
const mumkin = xato instanceof ApiXatosi
&& QAYTA_KODLAR.has(xato.kod);
if (!mumkin || urinish > marta) throw xato;
console.log(`${urinish}-urinish: ${xato.kod}, kutamiz`);
await kut(200 * urinish);
}
}
}
let chaqiruv = 0;
const beqaror = async () => {
chaqiruv++;
if (chaqiruv < 3) {
throw new ApiXatosi("Server xatosi", {
holat: 503, kod: "SERVER",
});
}
return "Osh";
};
console.log("Natija:", await qaytaUrin(beqaror, 3));
try {
await qaytaUrin(async () => {
throw new ApiXatosi("Taom topilmadi", {
holat: 404, kod: "TOPILMADI",
});
}, 3);
} catch (xato) {
console.log("Darhol:", xato.kod);
}Konsolda:
1-urinish: SERVER, kutamiz
2-urinish: SERVER, kutamiz
Natija: Osh
Darhol: TOPILMADISikl for (let urinish = 1; ; urinish++) — shartsiz, undan return yoki throw bilan chiqiladi. Shart urinish > marta — marta ta qayta urinishdan keyin taslim bo'lamiz. Holat 404 ni qayta so'rash befoyda: taom 200 ms da paydo bo'lib qolmaydi.
3-mashq (qiyin): Maydon xatolari
Server 422 da {xato, maydonlar} qaytaradi: {"matn": "1 dan 200 gacha belgi bo'lishi kerak"}. Hozirgi ApiXatosi maydonlar ni yo'qotadi. sorovYubor ni o'zgartiring: ApiXatosi ga uchinchi xususiyat — maydonlar (standarti {}) qo'shilsin va server yuborgan bo'lsa to'ldirilsin. Sinov: /bronlar ga bo'sh JSON ({}) yuborib, xato.kod va maydon nomlarini chiqaring.
Ishora: sorovYubor ga metod va tana ni «Ma'lumot yuborish» bo'limidagidek qo'shing. Object.keys(xato.maydonlar) — maydon nomlari.
Yechim
const ASOSIY_URL = "https://ilmhamroh.uz/api/mashq";
class ApiXatosi extends Error {
constructor(xabar, sozlama) {
super(xabar, sozlama);
this.name = "ApiXatosi";
this.holat = sozlama?.holat ?? 0;
this.kod = sozlama?.kod ?? "NOMALUM";
this.maydonlar = sozlama?.maydonlar ?? {};
}
}
async function sorovYubor(yol, { metod = "GET", tana } = {}) {
const sozlama = { method: metod, headers: {} };
if (tana !== undefined) {
sozlama.headers["Content-Type"] = "application/json";
sozlama.body = JSON.stringify(tana);
}
const javob = await fetch(ASOSIY_URL + yol, sozlama);
const malumot = await javob.json();
if (!javob.ok) {
throw new ApiXatosi(malumot.xato, {
holat: javob.status,
kod: javob.status === 422 ? "NOTOGRI" : "HTTP",
maydonlar: malumot.maydonlar,
});
}
return malumot;
}
try {
await sorovYubor("/bronlar", { metod: "POST", tana: {} });
} catch (xato) {
console.log(xato.kod, xato.holat, xato.message);
console.log(Object.keys(xato.maydonlar));
}Konsolda:
NOTOGRI 422 Ma'lumot noto'g'ri
[ 'ism', 'odamlar', 'sana', 'vaqt' ]Endi forma har maydon yonida o'z xabarini ko'rsata oladi — Constraint Validation API dagi kabi, faqat xabar serverdan. Server tekshiruvi brauzer tekshiruvining o'rnini bosmaydi va aksincha: brauzerdagisi tezlik uchun, serverdagisi xavfsizlik uchun.
4-mashq: Vazifalar qadami — api.js
vazifalar hozir ma'lumotni faqat xotirada saqlaydi. 32-darsda u serverga ulanadi. Bugun uning "ofitsianti"ni yozamiz — yangi modul assets/js/api.js. Talablar:
- Yangi branch:
git switch -c feature/api-klient. export class ApiXatosi extends Error—holat(HTTP kod yoki0) vakod(TARMOQ,VAQT,BEKOR,TOPILMADI,NOTOGRI,SERVER,HTTP,JAVOB).export function apiKlient({ asosiyUrl, kalit })qaytaradigan obyekt —{ royxat, qosh, yangila, ochir }. Metodlar:royxat()—GET /vazifalar/:kalit→{ vazifalar, versiya }.qosh(matn)—POST→ yangi vazifa (idni server beradi).yangila(id, ozgarish)—PATCH /vazifalar/:kalit/:id({ bajarildi: true }yoki{ matn }) → yangilangan vazifa.ochir(id)—DELETE→null(server204qaytaradi).
- Har so'rovda
AbortSignal.timeout(8 soniya); har metod oxirgi parametrsozlamadasignalvakutishqabul qilsin. tekshiruv/api.js— Node 24 da haqiqiy mashq API bilan sinov.
index.html ga hozircha hech narsa qo'shmang: api.js ni hali hech kim import qilmaydi. Uni 32-darsda asosiy.js ulaydi va o'shanda modulepreload ham qo'shiladi.
Diqqat: Mashq API vazifalarni server xotirasida saqlaydi. Server qayta ishga tushsa yoki ro'yxatga 7 kun tegilmasa — u o'chadi. Sinov va o'rganish uchun bu yetarli; haqiqiy ma'lumot uchun o'z backend'ingiz va bazangiz kerak — 25-qismdan boshlab quramiz.
Yechim
// api.js — server bilan gaplashadigan yagona joy
const KUTISH_MS = 8000;
export class ApiXatosi extends Error {
constructor(xabar, sozlama) {
super(xabar, sozlama);
this.name = "ApiXatosi";
this.holat = sozlama?.holat ?? 0;
this.kod = sozlama?.kod ?? "NOMALUM";
}
}
function holatKodi(holat) {
if (holat === 404) return "TOPILMADI";
if (holat === 400 || holat === 422) return "NOTOGRI";
if (holat >= 500) return "SERVER";
return "HTTP";
}
function tarmoqXatosi(xato) {
if (xato.name === "TimeoutError") {
return new ApiXatosi("Server javob bermadi.", {
kod: "VAQT", cause: xato,
});
}
if (xato.name === "AbortError") {
return new ApiXatosi("So'rov bekor qilindi.", {
kod: "BEKOR", cause: xato,
});
}
return new ApiXatosi("Internet aloqasini tekshiring.", {
kod: "TARMOQ", cause: xato,
});
}
async function sorovYubor(url, sozlama = {}) {
const { metod = "GET", tana, signal, kutish = KUTISH_MS } = sozlama;
const vaqt = AbortSignal.timeout(kutish);
const sorov = {
method: metod,
headers: { Accept: "application/json" },
signal: signal ? AbortSignal.any([signal, vaqt]) : vaqt,
};
if (tana !== undefined) {
sorov.headers["Content-Type"] = "application/json";
sorov.body = JSON.stringify(tana);
}
let javob;
let matn;
try {
javob = await fetch(url, sorov);
matn = await javob.text();
} catch (xato) {
throw tarmoqXatosi(xato);
}
let malumot = null;
if (matn !== "") {
try {
malumot = JSON.parse(matn);
} catch (xato) {
throw new ApiXatosi("Server JSON qaytarmadi.", {
holat: javob.status, kod: "JAVOB", cause: xato,
});
}
}
if (!javob.ok) {
const xabar = malumot?.xato ?? `Server xatosi: ${javob.status}`;
throw new ApiXatosi(xabar, {
holat: javob.status, kod: holatKodi(javob.status),
});
}
return malumot;
}
export function apiKlient({ asosiyUrl, kalit }) {
const manzil = `${asosiyUrl}/vazifalar/${kalit}`;
return {
royxat: (sozlama) => sorovYubor(manzil, sozlama),
qosh: (matn, sozlama) => sorovYubor(manzil, {
...sozlama, metod: "POST", tana: { matn },
}),
yangila: (id, ozgarish, sozlama) =>
sorovYubor(`${manzil}/${id}`, {
...sozlama, metod: "PATCH", tana: ozgarish,
}),
ochir: (id, sozlama) => sorovYubor(`${manzil}/${id}`, {
...sozlama, metod: "DELETE",
}),
};
}Eksport qilinadigani ikkita: ApiXatosi va apiKlient. sorovYubor, holatKodi, tarmoqXatosi — modulning ichki ishi, tashqaridan ko'rinmaydi. Bu 10-qismdagi "ochiq qism (public API)" g'oyasi.
Accept: "application/json" sarlavhasi serverga "men JSON kutaman" deydi. Mashq API'ga baribir, lekin ba'zi serverlar shunga qarab HTML yoki JSON qaytaradi.
Sinov fayli — tekshiruv/api.js. Har sinov uchun yangi crypto.randomUUID() kalit: ro'yxat har safar bo'sh boshlanadi.
// tekshiruv/api.js — api.js haqiqiy mashq API bilan
// Ishga tushirish: node tekshiruv/api.js
import { apiKlient, ApiXatosi } from "../assets/js/api.js";
const api = apiKlient({
asosiyUrl: "https://ilmhamroh.uz/api/mashq",
kalit: crypto.randomUUID(),
});
async function xatoniKor(nomi, ish) {
try {
await ish();
console.log(`${nomi}: xato chiqmadi ❌`);
} catch (xato) {
const belgi = xato instanceof ApiXatosi ? "✅" : "❌";
const { kod, holat, message } = xato;
console.log(`${nomi}: ${belgi} ${kod} ${holat} ${message}`);
}
}
const bosh = await api.royxat();
console.log("Boshida:", bosh.vazifalar.length, "ta");
const non = await api.qosh("Non olish");
const choy = await api.qosh("Choy damlash");
console.log("Qo'shildi:", non.id, non.matn, "|", choy.id, choy.matn);
const belgilandi = await api.yangila(non.id, { bajarildi: true });
console.log("Belgilandi:", belgilandi.matn, belgilandi.bajarildi);
console.log("O'chirildi:", await api.ochir(choy.id));
const oxiri = await api.royxat();
console.log("Oxirida:", oxiri.vazifalar.map((v) => v.matn));
await xatoniKor("Bo'sh matn", () => api.qosh(" "));
await xatoniKor("Yo'q id", () => api.ochir(999));
await xatoniKor("Vaqt", () => api.royxat({ kutish: 1 }));
await xatoniKor("Bekor", () =>
api.royxat({ signal: AbortSignal.abort() }));
const yomon = apiKlient({
asosiyUrl: "https://mavjud-emas.invalid", kalit: "sinov-kalit",
});
await xatoniKor("Tarmoq", () => yomon.royxat());node tekshiruv/api.js — Konsolda:
Boshida: 0 ta
Qo'shildi: 1 Non olish | 2 Choy damlash
Belgilandi: Non olish true
O'chirildi: null
Oxirida: [ 'Non olish' ]
Bo'sh matn: ✅ NOTOGRI 422 Ma'lumot noto'g'ri
Yo'q id: ✅ TOPILMADI 404 Vazifa topilmadi
Vaqt: ✅ VAQT 0 Server javob bermadi.
Bekor: ✅ BEKOR 0 So'rov bekor qilindi.
Tarmoq: ✅ TARMOQ 0 Internet aloqasini tekshiring.Hamma muvaffaqiyatsizlik turi bitta ApiXatosi bo'lib keldi — interfeys uchun shu kerak edi. Sozlama kutish: 1 — 1 ms timeout, hech bir server ulgurmaydi: VAQT ni sinashning eng oddiy yo'li. Metod AbortSignal.abort() — allaqachon bekor qilingan signal. Domen .invalid internetda hech qachon mavjud bo'lmaydi (RFC 2606) — tarmoq xatosi kafolatlangan.
git add assets/js/api.js tekshiruv/api.js
git commit -m "API klient: ApiXatosi va apiKlient"
git switch main
git merge feature/api-klient
git branch -d feature/api-klient
git push11. Real ishda
- Har jiddiy frontend loyihada
api/papkasi yokiapiClient.tsfayli bor. Payme, Uzum, Click'ning veb-ilovalarida ham interfeysfetchni to'g'ridan-to'g'ri chaqirmaydi — biror klient orqali. - Tayyor klientlar. Ko'p jamoalar o'ramni o'zi yozmaydi, tayyor kutubxona oladi: Axios (keyingi dars),
ky,ofetch. Ular bugungi g'oyalarning hammasini — asosiy URL, timeout, xato obyekti — tayyor beradi. - Server holati kutubxonalari. React'da "yuklanmoqda / xato / ma'lumot" holatlari, keshlash va race condition'dan himoyani TanStack Query avtomatik bajaradi (TanStack Query I). U ham ichida aynan siz yozgan klient funksiyalarini chaqiradi.
- TypeScript. Klient qatlami — serverdan kelgan "noma'lum" ma'lumotga tur beriladigan joy (API ma'lumotlarini tiplash).
- Intervyu. "Race condition'ni qanday oldini olasiz?", "Fetch xatolarini qanday boshqarasiz?" — frontend intervyularida juda ko'p so'raladi.
Xulosa
- API klient qatlami —
fetchuchraydigan yagona joy: asosiy URL, JSON,javob.ok, timeout bir marta yoziladi. - Besh xil muvaffaqiyatsizlik (tarmoq, timeout, bekor, HTTP xato, buzuq JSON) — bitta
ApiXatosi(holat,kod,message) ga keltiriladi; asl xato —causeda. AbortSignal.timeout+AbortSignal.any— timeout va tashqi bekor qilish birga.- Interfeysda uch holat: yuklanmoqda (tugma o'chiq), xato (xabar + qayta urinish), ma'lumot;
finallyholatni albatta tozalaydi. - Race condition — eski javob yangisini bosib ketadi; yechim: eskisini
abort()yoki so'rov raqami. - Server matni — faqat
textContent; maxfiy kalit — hech qachon frontend'da emas.
Keyingi dars: Axios — bugun qo'lda yozgan klientimizning mashhur tayyor varianti: instance, baseURL, interceptor'lar va error.response.
Manbalar
- MDN: "Using the Fetch API", "AbortSignal.timeout()", "AbortSignal.any()" — developer.mozilla.org
- WHATWG Fetch Standard — fetch.spec.whatwg.org
- RFC 2606: "Reserved Top Level DNS Names" (
.invalid) — rfc-editor.org - Kent C. Dodds, "Replace axios with a simple custom fetch wrapper" — kentcdodds.com/blog
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!