Mundarija (35)
- Bu darsda
- 1. Nega bu kerak?
- 2. Node'dagi taymer — obyekt
- 2.1 Son emas, Timeout obyekti
- 2.2 unref: jarayonni ushlab turmaslik
- 2.3 refresh: hisobni qaytadan boshlash
- 3. timers/promises: await qilinadigan taymer
- 3.1 Uch funksiya
- 3.2 Bekor qilish: { signal }
- 4. AbortSignal.timeout va AbortSignal.any
- 4.1 O'zi bekor bo'ladigan signal
- 4.2 Bir nechta signaldan biri: AbortSignal.any
- 4.3 Kim qaysi xato nomini beradi
- 5. Taymer aniqligi: o'lchov
- 5.1 "Kamida", "aynan" emas
- 5.2 Bloklangan event loop taymerni kechiktiradi
- 5.3 Juda katta kechikish
- 6. Qayta urinish: eksponensial kutish
- 6.1 G'oya
- 6.2 Jonli model
- 7. Testda vaqtni boshqarish: biz topgan nozik joy
- 8. CLI qadami: timeout va qayta urinish (holat 24)
- 8.1 src/urinish.ts
- 8.2 src/manba.ts: bitta so'rov
- 8.3 Testlar va topilma
- 8.4 Haqiqiy chiqish
- 9. Ko'p uchraydigan xatolar
- 10. Mashqlar
- 1-mashq (oson): qaysi vosita?
- 2-mashq (o'rta): withTimeout
- 3-mashq (qiyin): faqat vaqtinchalik xatolarga qayta urinish
- 4-mashq: CLI qadami — timeout va qayta urinish
- 11. Real ishda
- Xulosa
- Manbalar
Node.js taymerlari: timers/promises, AbortSignal.timeout, unref va qayta urinish
Qisqacha:
node:timers/promisestaymerlarniawaitqilinadigan qiladi:await setTimeout(300). Har kutishni{ signal }bilan bekor qilish mumkin,AbortSignal.timeout(5000)esa "5 soniyadan keyin o'zi bekor bo'ladigan" signal beradi.unref()taymerga "jarayonni ushlab turma" deydi. Taymer hech qachon erta ishlamaydi, lekin kechikishi mumkin: biz Windows'da o'lchadik —setTimeout(1)aslida ~15 ms kutdi (taymer qadami ~15.6 ms).
Bu darsda
timers/promisesdagisetTimeout,setImmediatevasetInterval(async iterator) bilan ishlaysiz.AbortController,AbortSignal.timeoutvaAbortSignal.anybilan kutishni va so'rovni bekor qilasiz.unref,ref,hasRefvarefreshnima uchun kerakligini tushuntira olasiz.- Taymer aniqligi qayerda buziladi — o'lchov bilan ko'rsatasiz.
valyutaga timeout va eksponensial kutishli qayta urinish qo'shasiz.
Oldin bilishingiz kerak: Taymerlar: setTimeout va setInterval, Bekor qilish: AbortController, Microtask, process.nextTick va setImmediate, readline va terminal.
1. Nega bu kerak?
valyuta CBU API'sidan kurs oladi. Internet har doim ham yaxshi emas: Chilonzordagi Wi-Fi bir soniya uzilib qolishi, CBU serveri bir lahzaga 503 qaytarishi mumkin. Hozir valyuta bunday paytda nima qiladi? fetch javob kelguncha cheksiz kutadi. Javob umuman kelmasa — foydalanuvchi qotib qolgan terminalga qarab o'tiradi.
Bizga uchta narsa kerak:
- Kutish chegarasi (timeout) — 5 soniyada javob bo'lmasa, to'xtash.
- Qayta urinish (retry) — vaqtinchalik xatoda yana urinish, lekin darhol emas: biroz kutib.
- Bekor qilish — kutish paytida Ctrl+C bosilsa, hammasi darhol to'xtashi.
Uchalasi ham taymerlar ustida quriladi. Taymerlar darsida setTimeout va setInterval ni brauzerda ko'rgansiz. Node'da ular bir xil ko'rinadi, lekin ichida farqlar bor. Shu farqlar va timers/promises moduli — bu darsning mavzusi.
O'xshatish: taksi chaqirdingiz. "10 daqiqada kelmasa — bekor qilaman" — bu timeout. "Band bo'lsa, 2 daqiqadan keyin yana chaqiraman, keyin 4 daqiqadan keyin" — bu eksponensial qayta urinish. "Uydan chiqmay qoldim" — bu tashqi bekor qilish.
2. Node'dagi taymer — obyekt
2.1 Son emas, Timeout obyekti
Brauzerda setTimeout son qaytaradi (taymer identifikatori). Node'da — obyekt:
const timer = setTimeout(() => {}, 1000);
console.log(typeof timer, timer.constructor.name); // object Timeout
console.log(timer.hasRef()); // true
clearTimeout(timer);clearTimeout(timer) ikkalasida ham bir xil ishlaydi. Obyektning esa qo'shimcha metodlari bor: ref, unref, hasRef, refresh. TypeScript'da shuning uchun taymer turi ReturnType<typeof setTimeout> deb yoziladi — brauzer va Node'da har xil.
2.2 unref: jarayonni ushlab turmaslik
Event loop fazalari darsidan eslang: Node jarayoni event loop'da bajariladigan ish qolmaguncha tugamaydi. Ochiq taymer — shunday ish. unref() esa aytadi: "bu taymer jarayonni yashashga majburlamasin".
setTimeout(() => console.log("bu chiqmaydi"), 1000).unref();
setInterval(() => console.log("bu ham"), 500).unref();
console.log("dastur tugadi");Konsolda:
dastur tugadiDastur darhol tugadi — ikkala taymer ham kutilmadi. Qachon kerak? Fon ishlari uchun: har 10 daqiqada eski keshni tozalaydigan interval, "tirikman" signalini yuboradigan heartbeat. Ular foydali, lekin faqat ular qolgan bo'lsa — dasturni tugatish kerak. ref() teskarisini qiladi, hasRef() hozirgi holatni aytadi.
2.3 refresh: hisobni qaytadan boshlash
timer.refresh() taymerni o'chirmaydi va yangisini yaratmaydi — hisobni hozirdan qaytadan boshlaydi. Masalan, "foydalanuvchi 300 ms yozmay tursa — saqlash":
const start = Date.now();
const timer = setTimeout(() => {
const ms = Math.round((Date.now() - start) / 100) * 100;
console.log(`saqlandi: ${ms} ms da`);
}, 300);
setTimeout(() => timer.refresh(), 200); // 200 ms da yana harf yozildiKonsolda:
saqlandi: 500 ms daVaqt bo'yicha ketma-ketlik:
| Vaqt | Nima bo'ldi | Taymer qachon ishlaydi |
|---|---|---|
| 0 ms | setTimeout(…, 300) |
300 ms da |
| 200 ms | timer.refresh() — "yana harf yozildi" |
200 + 300 = 500 ms da |
| 500 ms | callback | saqlandi: 500 ms da |
300 ms o'rniga 500: 200-millisekundda hisob qayta boshlandi va yana 300 ms kutildi. Bu Debounce va throttle darsidagi debounce'ning eng arzon ko'rinishi — yangi obyekt yaratilmaydi. Natija 100 ga yaxlitlangan: taymer bir necha millisekund kechikishi mumkin (pastda o'lchaymiz).
Tekshirib ko'ring: CLI dasturida har 30 soniyada "hali ishlayapman..." deb yozadigan
setIntervalbor. Asosiy ish 2 soniyada tugadi, lekin dastur tugamayapti. Nima qilish kerak?
Javob
Ikki yo'l: ish tugaganda clearInterval(id) chaqirish yoki intervalni yaratishda .unref() qilish. unref qulayroq — qaysi yo'l bilan tugashidan qat'i nazar (xato bilan ham), interval jarayonni ushlab turmaydi.
3. timers/promises: await qilinadigan taymer
3.1 Uch funksiya
Promise yordamchilari darsida setTimeout ni qo'lda Promise'ga o'raganmiz: new Promise((r) => setTimeout(r, ms)). Node buni tayyor beradi:
import {
setTimeout as sleep,
setImmediate as immediate,
setInterval as every,
} from "node:timers/promises";
console.log(await sleep(50, "tayyor"));
console.log(await immediate("navbatdagi aylanishda"));
let n = 0;
for await (const tick of every(20, "tik")) {
n++;
console.log(n, tick);
if (n === 3) break;
}Konsolda:
tayyor
navbatdagi aylanishda
1 tik
2 tik
3 tikE'tibor bering:
- Nom to'qnashuvi. Global
setTimeoutbilan adashmaslik uchun import'ni qayta nomlaymiz:setTimeout as sleep.valyutada ukutdeb ataladi. - Ikkinchi argument — qiymat. Promise shu qiymat bilan bajariladi. Callback yo'q.
setInterval— async iterator.for awaithar20ms da bir aylanadi.breakqilganda interval o'zi tozalanadi —clearIntervalkerak emas.
3.2 Bekor qilish: { signal }
Uchinchi argument — sozlamalar. Eng muhimi signal (AbortController darsidan tanish):
import { setTimeout as sleep } from "node:timers/promises";
const controller = new AbortController();
const waiting = sleep(10_000, null, { signal: controller.signal });
controller.abort();
try {
await waiting;
} catch (error) {
console.log(error.name, error.code); // AbortError ABORT_ERR
}10 soniyalik kutish darhol tugadi — AbortError bilan. Taymer ham tozalandi. Shu yo'l bilan Ctrl+C kutishni ham uzadi: signalni bekor qilasiz, hamma await darhol uyg'onadi.
Yana bitta sozlama — ref: false. Bu unref() ning promise ko'rinishi: kutish jarayonni ushlab turmaydi.
4. AbortSignal.timeout va AbortSignal.any
4.1 O'zi bekor bo'ladigan signal
Timeout uchun AbortController + setTimeout + clearTimeout yozish shart emas. AbortSignal.timeout(ms) — belgilangan vaqtdan keyin o'zi bekor bo'ladigan tayyor signal. U brauzerda ham bor, shuning uchun bu blok saytda ham ishlaydi:
const signal = AbortSignal.timeout(50);
console.log(signal.aborted); // false
await new Promise((r) => setTimeout(r, 100));
console.log(signal.aborted); // true
console.log(signal.reason.name); // TimeoutErrorSabab (reason) — TimeoutError, oddiy abort() dagi AbortError emas. Shunday qilib "vaqt tugadi" va "foydalanuvchi bekor qildi" ni farqlash mumkin.
4.2 Bir nechta signaldan biri: AbortSignal.any
Bizga ikkalasi birga kerak: "5 soniya o'tsa YOKI Ctrl+C bosilsa". AbortSignal.any([...]) — ro'yxatdagi istalgan biri bekor bo'lsa, o'zi ham bekor bo'ladigan signal:
const user = new AbortController();
const limit = AbortSignal.timeout(5000);
const both = AbortSignal.any([user.signal, limit]);
user.abort(new Error("Ctrl+C bosildi"));
console.log(both.aborted); // true
console.log(both.reason.message); // Ctrl+C bosildiSabab birinchi bekor bo'lgan signaldan o'tadi. valyuta dagi so'rov aynan shunday quriladi (pastda).
4.3 Kim qaysi xato nomini beradi
Bitta nozik joy — biz ikkala holatni ishga tushirib ko'rdik (Node 24.21.0):
| Nima kutildi | Signal AbortSignal.timeout bilan tugasa |
Xato |
|---|---|---|
fetch(url, { signal }) |
TimeoutError |
The operation was aborted due to timeout |
sleep(ms, null, { signal }) |
AbortError, cause — TimeoutError |
The operation was aborted |
fetch sababni to'g'ridan beradi, timers/promises esa uni cause ichiga o'raydi. Shuning uchun "vaqt tugadimi?" ni xato nomiga qarab emas, signalning o'ziga qarab tekshirish ishonchliroq: signal.aborted va signal.reason. valyuta ham shunday qiladi.
Tekshirib ko'ring:
AbortSignal.any([a, b])daa1 soniyada,b3 soniyada bekor bo'ladi. Natija signal qachon bekor bo'ladi va sababi qaysi?
Javob
1 soniyada, sababi — a.reason. any birinchi bekor bo'lgan signalga ergashadi; b keyin bekor bo'lsa ham, natija o'zgarmaydi — signal faqat bir marta bekor bo'ladi.
5. Taymer aniqligi: o'lchov
5.1 "Kamida", "aynan" emas
Node hujjati aniq: callback aynan delay millisekundda chaqirilishi kafolatlanmaydi — faqat undan oldin emas. Biz qanchaga kechikishini o'lchadik: har kechikishni 20 marta await sleep(d) bilan kutdik va haqiqiy vaqtning medianasini oldik (Node 24.21.0, Windows 11, i5-12500H):
import { setTimeout as sleep } from "node:timers/promises";
async function measure(delay, times) {
const gaps = [];
for (let i = 0; i < times; i++) {
const t = performance.now();
await sleep(delay);
gaps.push(performance.now() - t);
}
gaps.sort((a, b) => a - b);
const median = gaps[times >> 1].toFixed(1);
return `kutildi ${delay} ms → mediana ${median}`;
}
for (const d of [1, 5, 10, 16, 50, 100]) {
console.log(await measure(d, 20));
}Biz ishga tushirgan chiqish:
kutildi 1 ms → mediana 15.3
kutildi 5 ms → mediana 15.4
kutildi 10 ms → mediana 15.7
kutildi 16 ms → mediana 30.9
kutildi 50 ms → mediana 62.1
kutildi 100 ms → mediana 108.5Grafikda ko'rinadi — so'ralgan va haqiqiy kutish:
- 1 ms15,3 ms
- 5 ms15,4 ms
- 10 ms15,7 ms
- 16 ms30,9 ms
- 50 ms62,1 ms
- 100 ms108,5 ms
Manba: IlmHamroh o'lchovi, 2026-10-23: Node 24.21.0, Windows 11 Pro, i5-12500H, har biri 20 marta
1, 5 va 10 ms — hammasi 15.5 ms. 16 ms — ~31 ms, ya'ni ikki "qadam". Windows'da Node taymerlari ~15.6 ms lik qadamlar bilan ishlayapti: so'ralgan vaqt keyingi qadamgacha yaxlitlanadi. Natijada 1555 ms** davom etdi (ikki marta o'lchadik: 1553 va 1557).setInterval(fn, 10) 100 marta — 1000 emas, **
Linux va macOS'da biz o'lchamadik (WSL/Docker yo'q edi). U yerda qadam odatda ancha mayda — lekin bu taxmin, o'z mashinangizda shu skriptni ishga tushirib ko'ring.
5.2 Bloklangan event loop taymerni kechiktiradi
Ikkinchi sabab — kod. Taymer callback'i faqat event loop bo'shaganda bajariladi (Event loop'ni bloklash):
const start = performance.now();
setTimeout(() => {
const ms = (performance.now() - start).toFixed(1);
console.log(`100 ms taymer: ${ms} ms da`);
}, 100);
const t = Date.now();
while (Date.now() - t < 300); // 300 ms og'ir hisobBiz ishga tushirdik: 100 ms taymer: 305.3 ms da. Taymer 100-millisekundda tayyor edi, lekin while tugaguncha navbatda turdi.
5.3 Juda katta kechikish
Kechikish 32 bitli butun songa sig'ishi kerak: eng ko'pi 2 ** 31 - 1 ms, ~24.8 kun. Undan katta son bersangiz, Node ogohlantiradi va kutishni 1 ms qiladi (biz ishga tushirdik):
(node:17936) TimeoutOverflowWarning: 2147483648 does not fit into a 32-bit signed integer.
Timeout duration was set to 1.Tarjimasi: "2147483648 32 bitli ishorali butun songa sig'maydi. Kutish 1 ga o'rnatildi". Ya'ni "25 kundan keyin eslat" degan taymer darhol ishlaydi. Uzoq muddatli rejalar uchun taymer emas, sana saqlanadi va davriy tekshiriladi.
Tekshirib ko'ring: Windows'da
await sleep(10)ni 100 marta ketma-ket chaqirsangiz, taxminan qancha vaqt ketadi?
Javob
Taxminan 1.5 soniya, 1 soniya emas: o'lchovda har sleep(10) ~15.7 ms davom etdi. 100 × 15.7 ≈ 1570 ms. Shuning uchun "har 10 ms da" ishlaydigan narsalarni taymerlar soniga emas, haqiqiy vaqtga (performance.now()) qarab hisoblang.
6. Qayta urinish: eksponensial kutish
6.1 G'oya
Server 503 qaytardi. Darhol qayta urinish — yomon fikr: server allaqachon yuklangan, minglab mijoz bir vaqtda qayta ursa, uni butunlay yiqitadi. To'g'ri yo'l:
- Eksponensial kutish (exponential backoff) — har safar ikki baravar ko'p kutish: 300, 600, 1200 ms.
- Jitter (tasodifiy siljish) — kutishga ± 20 % tasodif qo'shish. Shunda bir vaqtda xato olgan mijozlar bir vaqtda qaytib kelmaydi.
- Faqat vaqtinchalik xatoda. 404 "bunday narsa yo'q" — necha marta so'rasangiz ham yo'q. 503, timeout, tarmoq uzilishi — vaqtinchalik.
sequenceDiagram
participant V as valyuta
participant C as CBU
V->>C: 1-urinish
C-->>V: 503
Note over V: ~300 ms kutish
V->>C: 2-urinish
C-->>V: 503
Note over V: ~600 ms kutish
V->>C: 3-urinish
C-->>V: 200 OKDiagrammada kutish har safar ikki baravar oshadi. Uchinchi urinish ham yiqilsa — xato yuqoriga uchadi va valyuta keshga yoki fixture'ga o'tadi.
6.2 Jonli model
Quyidagi model brauzerda ham ishlaydi: timers/promises o'rniga new Promise((r) => setTimeout(r, ms)). Tasodifni parametr qilib berdik — natija takrorlanadigan bo'lsin:
class TransientError extends Error {}
const wait = (ms) => new Promise((r) => setTimeout(r, ms));
async function retry(work, { attempts = 3, baseMs = 30, random }) {
for (let n = 1; ; n++) {
try {
return await work(n);
} catch (error) {
const transient = error instanceof TransientError;
if (n >= attempts || !transient) throw error;
const jitter = 0.8 + random() * 0.4;
const delay = Math.round(baseMs * 2 ** (n - 1) * jitter);
console.log(`${n}-urinish: ${error.message}, ${delay} ms`);
await wait(delay);
}
}
}
const answers = [503, 503, 200];
const result = await retry(async (n) => {
const status = answers[n - 1];
if (status >= 500) throw new TransientError(`${status}`);
return `${n}-urinishda OK`;
}, { random: () => 0.5 });
console.log(result);Konsolda:
1-urinish: 503, 30 ms
2-urinish: 503, 60 ms
3-urinishda OKrandom: () => 0.5 — jitter koeffitsiyenti aniq 1.0. Haqiqiy kodda Math.random beriladi. Javoblar ro'yxatini [404] qilib ko'ring: birinchi urinishdayoq xato tashlanadi, chunki Error — TransientError emas.
7. Testda vaqtni boshqarish: biz topgan nozik joy
Qayta urinishni test qilish sekin: haqiqiy kutishlar 300 + 600 ms. node:test da soxta soat bor — mock.timers. U setTimeout ni almashtiradi va vaqtni tick(ms) bilan qo'lda oldinga suradi. Vitest'dagi soxta taymerlar bilan bir xil g'oya.
Biz uni timers/promises bilan sinab ko'rdik va nozik joy topdik (Node 24.21.0):
import { test, mock } from "node:test";
import timersPromises from "node:timers/promises";
import { setTimeout as named } from "node:timers/promises";
test("default import", async () => {
mock.timers.enable({ apis: ["setTimeout"] });
const p = timersPromises.setTimeout(60_000);
mock.timers.tick(60_000);
await p; // darhol
mock.timers.reset();
});
test("nomli import", async () => {
mock.timers.enable({ apis: ["setTimeout"] });
const p = named(3000);
mock.timers.tick(3000);
await p; // haqiqatan 3 soniya kutdi!
mock.timers.reset();
});Biz ishga tushirdik (node --test):
✔ default import (1.8956ms)
✔ nomli import (3014.4562ms)Nimaga qarash kerak: qavs ichidagi vaqtga. Birinchi test 2 ms da tugadi, ikkinchisi — 3 014 ms da.
Nima bo'ldi? Soxta soat modul obyektidagi funksiyani almashtiradi. import { setTimeout as named } esa funksiyani import paytida olib qo'yadi — almashtirish unga yetib bormaydi. Ikkinchi test o'tdi, lekin 3 soniya haqiqiy vaqt sarfladi. Node hujjati ham buni ochiq yozadi: funksiyani destrukturizatsiya qilib olish (import { setTimeout } from …) mock.timers da "hozircha qo'llab-quvvatlanmaydi".
Biz yana bir holatni sinadik: import * as ns from "node:timers/promises" va ns.setTimeout(2000) — u ham 2 soniya haqiqiy kutdi (2 014 ms). Ishlaydigan yagona shakl — sukut import: import timersPromises from "node:timers/promises" va timersPromises.setTimeout(...).
Halol izoh:
valyutadagiurinish.ts(pastdagi «CLI qadami») aynan nomli import ishlatadi:import { setTimeout as kut }. Demak uning kutishinimock.timersbilan tezlashtirib bo'lmaydi. Kanon testlari shuning uchun haqiqiy vaqt bilan yozilgan: kutish 240–360 ms va ≥ 480 ms oralig'ida tekshiriladi, testlar 4.6 soniya oladi. Kanon kodini o'zgartirmaymiz. O'z loyihangizda soxta soat kerak bo'lsa — sukut importga o'ting (4-mashqdan keyin sinab ko'ring).
8. CLI qadami: timeout va qayta urinish (holat 24)
8.1 src/urinish.ts
Kanon fayli (kod aynan, izohlar qisqartirilgan):
// Qayta urinish: eksponensial kutish + jitter; faqat vaqtinchalik xatolarda
import { setTimeout as kut } from "node:timers/promises";
export class VaqtinchalikXato extends Error {}
export interface UrinishSozlamasi {
urinishlar?: number | undefined; // jami urinishlar (sukut 3)
asosMs?: number; // 1-kutish: 300, 600, 1200 … ± 20 %
signal?: AbortSignal | undefined;
urinishdan?: ((n: number, xato: unknown, kutishMs: number) => void) | undefined; // log uchun
}
export async function qaytaUrin<T>(ish: (urinish: number) => Promise<T>, s: UrinishSozlamasi = {}): Promise<T> {
const { urinishlar = 3, asosMs = 300, signal, urinishdan } = s;
for (let n = 1; ; n++) {
try {
return await ish(n);
} catch (xato) {
if (n >= urinishlar || !(xato instanceof VaqtinchalikXato) || signal?.aborted) throw xato;
const kutish = Math.round(asosMs * 2 ** (n - 1) * (0.8 + Math.random() * 0.4));
urinishdan?.(n, xato, kutish);
await kut(kutish, undefined, { signal }); // bekor qilinsa AbortError bilan chiqadi
}
}
}Jonli modeldan farqi bitta: kutish timers/promises bilan va { signal } bilan. Signal bekor qilinsa, kut kutish o'rtasida ham darhol AbortError tashlaydi.
Halol izoh: holat 24 da bu signalni hali hech kim bekor qilmaydi. Hozirgi
bin.tsCtrl+C da darholprocess.exit(130)qiladi (process darsi).signalparametri oldindan tayyorlab qo'yilgan. Uni Ctrl+C ga ulash — holat 46 da, Built-in fetch va undici darsida. Haqiqiy Ctrl+C (terminalda) kanonda sinalmagan — testlar signalniAbortControllerbilan bekor qiladi.
8.2 src/manba.ts: bitta so'rov
async function birSorov(url: string, kutishMs: number, signal?: AbortSignal): Promise<{ kurslar: Kurs[]; xom: string }> {
const vaqt = AbortSignal.timeout(kutishMs);
let javob: Response;
try {
javob = await fetch(url, { signal: signal ? AbortSignal.any([signal, vaqt]) : vaqt });
} catch (xato) {
if (signal?.aborted) throw xato; // foydalanuvchi bekor qildi — qayta urinmaymiz
const sabab = vaqt.aborted ? `${String(kutishMs)} ms ichida javob yo'q` : "tarmoq xatosi";
throw new VaqtinchalikXato(`CBU API: ${sabab}`, { cause: xato });
}
if (javob.status >= 500) throw new VaqtinchalikXato(`CBU API ${String(javob.status)} qaytardi`);
if (!javob.ok) throw new Error(`CBU API ${String(javob.status)} qaytardi`);
const xom = await javob.text();
return { kurslar: royxatniOqi(JSON.parse(xom)), xom };
}Xatolar uch guruhga bo'linadi:
| Holat | Xato | Qayta urinish |
|---|---|---|
| Timeout, tarmoq uzildi, 5xx | VaqtinchalikXato (cause — asl xato) |
ha |
| 4xx (404, 400) | oddiy Error |
yo'q |
Foydalanuvchi bekor qildi (signal) |
asl AbortError |
yo'q, keshga ham o'tmaydi |
"Vaqt tugadimi?" savoliga vaqt.aborted javob beradi — «Kim qaysi xato nomini beradi» bo'limidagi maslahat. apidanOl(url, { kutishMs = 5000, signal, urinishlar, urinishdan }) esa birSorov ni qaytaUrin ga o'raydi. CLI'da yangi muhit o'zgaruvchisi: VALYUTA_KUTISH (ms), har urinish stderr'ga yoziladi.
8.3 Testlar va topilma
Uchta yangi test (jami 33): 503, 503, 200 → 3 so'rov va 2 kutish (birinchisi 240–360, ikkinchisi ≥ 480 ms); 404 → 1 so'rov; 2 soniya kechikadigan server va kutishMs: 100 → VaqtinchalikXato, cause.name === "TimeoutError", jami 1.5 soniyadan kam. To'rtinchisi — tashqi AbortController 50 ms da bekor qiladi → AbortError, faqat 1 so'rov.
Soxta server uchun kanon ikki narsani topdi. Birinchisi: kechikkan javobli ulanish server.close() ni ushlab turadi — test oxirida closeAllConnections() kerak. Ikkinchisi muhimroq: qayta urinish qo'shilgach, eski test yiqildi. "Muddati o'tgan kesh, API yiqilsa eski kesh" testida server 3-so'rovga 503 berardi. Endi valyuta 4-so'rov bilan qayta urindi, u 200 oldi va natija api bo'ldi, kesh emas. Xulq to'g'ri — test eskirgan edi. Test tuzatildi: server 3-so'rovdan boshlab doim 503 qaytaradi. Yangi imkoniyat eski testni "buzsa", avval xulq to'g'rimi deb so'rang.
8.4 Haqiqiy chiqish
Kanon uch holatni ishga tushirdi (natijalar/24-jonli.txt). Birinchisi — ishlamaydigan manzil (127.0.0.1:9):
$ VALYUTA_API=http://127.0.0.1:9/ valyuta kurs USD
Qayta urinish 2: CBU API: tarmoq xatosi — 273 ms kutib
Qayta urinish 3: CBU API: tarmoq xatosi — 607 ms kutib
Ogohlantirish: API ishlamadi (CBU API: tarmoq xatosi) — saqlangan namuna kurslar (fixtures/) ishlatildi
2026-10-23 holatiga (fixture)
USD 1 11 903.15 -62.36 AQSH dollari
[exit 0, 1061 ms]Haqiqiy CBU, lekin VALYUTA_KUTISH=5 (5 ms — atayin juda qisqa):
Qayta urinish 2: CBU API: 5 ms ichida javob yo'q — 267 ms kutib
Qayta urinish 3: CBU API: 5 ms ichida javob yo'q — 673 ms kutibSukut sozlamalarda — (api), 406 ms. Kutishlar 273 va 607 — 300 va 600 ning ± 20 % i ichida, jitter ishlayapti. Bitta kamchilik ko'rinadi: "tarmoq xatosi" ning asl sababi yashiringan. U cause ichida saqlangan, uni Stack trace, source map va error.cause darsida ekranga chiqaramiz.
Bu yerda bir ogohlantirish. 127.0.0.1:9 ni "yopiq port" deb o'ylash tabiiy — lekin sabab boshqa: fetch 9-portga umuman ulanmaydi. Fetch standartida xavfli portlar ro'yxati bor va 9 ham unda. Shuning uchun asl sabab ECONNREFUSED ("ulanish rad etildi") emas, bad port ("ruxsat etilmagan port"). Buni net, dns va tls darsida o'z ko'zingiz bilan ko'rasiz. Test uchun bu farq muhim emas: ikkala holat ham "tarmoq xatosi" va qayta urinish ishlaydi.
Tekshiruv: tip va lint 0, testlar 33/33 (4.6 s — haqiqiy kutishlar), paket 47 fayl, 21.6 kB, sovuq start 78 ms.
Commit: Tarmoq: so'rov timeout (AbortSignal.timeout), 3 urinish eksponensial kutish bilan, faqat vaqtinchalik xatolarda
9. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| CLI ish tugagach ham ochiq turadi | Tozalanmagan setInterval |
clearInterval yoki .unref() |
fetch cheksiz kutadi |
Signal berilmagan | { signal: AbortSignal.timeout(ms) } |
| Ctrl+C bosildi, lekin qayta urinish davom etdi | kut ga signal berilmagan |
await kut(ms, undefined, { signal }) |
| 404 da ham 3 marta urinadi | Hamma xato qayta urinilyapti | Faqat vaqtinchalik xato klassi |
TimeoutOverflowWarning, taymer darhol ishladi |
Kechikish 24.8 kundan katta | Sanani saqlang, davriy tekshiring |
mock.timers ishlamaydi, test sekin |
timers/promises nomli import |
Modul obyekti orqali chaqiring yoki haqiqiy vaqt |
setTimeout(fn, 1) 15 ms kutdi |
Windows taymer qadami | Normal holat; aniq vaqtni performance.now() bilan o'lchang |
10. Mashqlar
Mashqlar kurs/mashqlar/24/24-timers/ da.
1-mashq (oson): qaysi vosita?
- 5 soniyadan keyin o'zi bekor bo'ladigan signal —
AbortSignal.(5000) - Ikki signaldan birortasi bekor bo'lsa bekor bo'ladigan signal —
AbortSignal.([a, b]) - Taymer jarayonni ushlab turmasin —
timer.() - Taymer hisobini qaytadan boshlash —
timer.() awaitqilinadigan taymerlar moduli —node:timers/
Yechim
- timeout — sabab
TimeoutError. - any — birinchi bekor bo'lganning sababi bilan.
- unref — fon ishlari uchun;
ref()qaytaradi. - refresh — yangi obyekt yaratmaydi.
- promises —
setTimeout,setImmediate,setInterval.
2-mashq (o'rta): withTimeout
withTimeout(promise, ms) funksiyasini yozing: va'da ms ichida bajarilsa — uning qiymati, aks holda Error("120 ms dan oshdi") (raqam — ms). Taymer har holda tozalansin, aks holda dastur ortiqcha kutadi. Promise.race (Promise kombinatorlari) ishlating. Sinang: 30 ms da tugaydigan va 300 ms da tugaydigan ish, chegara 120 ms.
Yechim
function withTimeout(promise, ms) {
let timer;
const limit = new Promise((_, reject) => {
const fail = () => reject(new Error(`${ms} ms dan oshdi`));
timer = setTimeout(fail, ms);
});
return Promise.race([promise, limit])
.finally(() => clearTimeout(timer));
}
const delay = (ms, v) =>
new Promise((r) => setTimeout(() => r(v), ms));
console.log(await withTimeout(delay(30, "kurs keldi"), 120));
try {
await withTimeout(delay(300, "kech"), 120);
} catch (error) {
console.log(error.message);
}Konsolda:
kurs keldi
120 ms dan oshdifinally ikkala holatda ham taymerni tozalaydi. Birinchi chaqiruvda usiz 120 ms lik taymer bekorga qolardi. Bu brauzerda ham ishlaydi. Node'da xuddi shuni AbortSignal.timeout bilan qisqaroq yozish mumkin — lekin faqat signal qabul qiladigan ishlar uchun (fetch, timers/promises).
3-mashq (qiyin): faqat vaqtinchalik xatolarga qayta urinish
retry modelini kengaytiring: (1) shouldRetry(error) funksiyasi parametr bo'lsin (sukut — har xatoga), (2) signal qabul qilsin va kutishda bekor bo'lsa darhol to'xtasin. Node'da timers/promises bilan yozing. Sinang: 503, 503, 200 → OK; 404 → 1 urinishda xato; uzun kutish paytida 20 ms da bekor qilingan signal → AbortError.
Yechim
import { setTimeout as sleep } from "node:timers/promises";
async function retry(work, opts = {}) {
const { attempts = 3, baseMs = 30, signal } = opts;
const shouldRetry = opts.shouldRetry ?? (() => true);
for (let n = 1; ; n++) {
try {
return await work(n);
} catch (error) {
if (n >= attempts || !shouldRetry(error)) throw error;
if (signal?.aborted) throw error;
await sleep(baseMs * 2 ** (n - 1), undefined, { signal });
}
}
}
const fromList = (list) => async (n) => {
const status = list[n - 1];
if (status === 200) return `${n}-urinishda OK`;
throw Object.assign(new Error(`${status}`), { status });
};
const only5xx = (e) => e.status >= 500;
const ok = fromList([503, 503, 200]);
console.log(await retry(ok, { shouldRetry: only5xx }));
await retry(fromList([404, 200]), { shouldRetry: only5xx })
.catch((e) => console.log("404:", e.message));
const signal = AbortSignal.timeout(20);
await retry(fromList([503, 503, 503]), { baseMs: 1000, signal })
.catch((e) => console.log("bekor:", e.name));Konsolda:
3-urinishda OK
404: 404
bekor: AbortErrorUchinchi holatda kutish 1000 ms bo'lishi kerak edi, lekin signal 20 ms da bekor bo'ldi — sleep darhol AbortError tashladi. Xato nomi AbortError, TimeoutError emas: «Kim qaysi xato nomini beradi» bo'limidagi jadvalni eslang.
4-mashq: CLI qadami — timeout va qayta urinish
O'z valyuta ingizda «CLI qadami» bo'limidagi qadamni bajaring: urinish.ts, manba.ts dagi birSorov va apidanOl, VALYUTA_KUTISH, to'rtta test. Eski "muddati o'tgan kesh" testi yiqilsa — nega yiqilganini tushuntirib, tuzating. Keyin:
VALYUTA_API=http://127.0.0.1:9/ node src/bin.ts kurs USD
npm run tip && npm run lint && npm testYechim
Kutilgan: ikkita Qayta urinish …: CBU API: tarmoq xatosi — N ms kutib qatori (N — taxminan 240–360 va 480–720), keyin Ogohlantirish: API ishlamadi … va fixture jadvali, exit 0; testlar 33/33. Eski test yiqilishi sababi: server 3-so'rovga 503 berardi, endi 4-so'rov (qayta urinish) 200 oladi. Server 3-so'rovdan boshlab doim 503 qaytarsin. Hammasi o'tsa — yuqoridagi commit xabari bilan commit qiling.
11. Real ishda
- Har tashqi so'rovda timeout — production qoidasi. Timeout'siz so'rov bitta sekin server tufayli butun ilovani "osib" qo'yishi mumkin.
- Exponential backoff + jitter — AWS, Google Cloud SDK'lari va ko'p HTTP klientlar shu usulda qayta urinadi. Backend qismlarida navbatlar va idempotentlik bilan qayta ko'ramiz.
unref— log yuboruvchi, metrika yig'uvchi kutubxonalar fon intervallariniunrefqiladi, aks holda ular sizning CLI'ingizni tugatmay qo'yardi.- Graceful shutdown — serverni to'xtatishda "10 soniyada tugamasa majburan chiqish" taymeri ham
unrefqilinadi. - Intervyuda: "Qayta urinishda nimalarga e'tibor berasiz?" — faqat vaqtinchalik xatolar, eksponensial kutish, jitter, urinishlar chegarasi, bekor qilish.
Xulosa
node:timers/promises:await setTimeout(ms, qiymat, { signal }),setImmediate,setInterval— async iterator (breaktozalaydi).AbortSignal.timeout(ms)— o'zi bekor bo'ladi (TimeoutError);AbortSignal.any— bir nechtasidan birinchisi.- Node taymeri — obyekt:
unrefjarayonni ushlab turmaydi,refreshhisobni qayta boshlaydi. - Taymer erta ishlamaydi, lekin kechikadi: Windows'da ~15.6 ms qadam, bloklangan loop esa yanada ko'p.
- Qayta urinish: faqat vaqtinchalik xatoda, eksponensial kutish va jitter bilan, signal kutishni ham uzadi.
Keyingi dars: child_process: tashqi dasturlarni ishga tushirish — Node'dan git, ffmpeg yoki boshqa Node jarayonini ishga tushirish va shell injection xavfi.
Manbalar
- Node.js hujjati — Timers (
Timeoutklassi:ref,unref,hasRef,refresh; aniqlik kafolati yo'qligi): https://nodejs.org/docs/latest-v24.x/api/timers.html - Node.js hujjati — Timers Promises API (
setTimeout,setImmediate,setInterval,signal,ref): https://nodejs.org/docs/latest-v24.x/api/timers.html#timers-promises-api - Node.js hujjati —
AbortSignal.timeout,AbortSignal.any: https://nodejs.org/docs/latest-v24.x/api/globals.html#class-abortsignal - Node.js hujjati — Test runner, Mocking timers (
mock.timers): https://nodejs.org/docs/latest-v24.x/api/test.html#class-mocktimers - Kanon:
valyutaholat 24 (urinish.ts,manba.ts,manba.test.ts),natijalar/24-jonli.txt,tekshiruv-24.txt. Taymer aniqligi, xato nomlari vamock.timerstopilmasi — biz ishga tushirdik (Node 24.21.0, Windows 11 Pro, i5-12500H, 2026-10-23).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!