Mundarija (25)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ikki hodisa
- 2.1 unhandledRejection — hech kim kutmagan Promise
- 2.2 Ishlovchisiz — Node 24 sukuti
- 2.3 uncaughtException — sinxron xato hech kim ushlamagan
- 3. Rejimlar: --unhandled-rejections
- 4. Nega "davom etish" xavfli
- 4.1 Yarim bajarilgan ish
- 4.2 Node hujjati nima deydi
- 4.3 Kim qayta ishga tushiradi?
- 5. process.exitCode va process.exit()
- 6. Kanon: src/jarayon.ts
- 6.1 server.ts endi qisqa
- 6.2 Test: haqiqiy jarayonda
- 7. API qadami: jarayon va commit (holat 34, 6-qism)
- 8. Ko'p uchraydigan xatolar
- 9. Mashqlar
- 1-mashq (oson): o'ladimi?
- 2-mashq (o'rta): xavfsizFon
- 3-mashq (qiyin): kichik "nazoratchi"
- 4-mashq: API qadami
- 10. Real ishda
- Xulosa
- Manbalar
Node.js process darajasidagi xatolar: uncaughtException, unhandledRejection va "let it crash"
Qisqacha: Express ushlay olmagan xato (taymer ichidagi
throw, hech kimawaitqilmagan rad etilgan Promise) jarayon darajasiga chiqadi. Node 24 sukuti: jarayon darholexit 1bilan o'ladi.process.on("unhandledRejection")yoki"uncaughtException"qo'shsangiz, jarayon o'lmaydi — lekin bu davolash emas: xatodan keyin xotiradagi holat noma'lum. To'g'ri strategiya — "let it crash": xatoni logga yozish, boshlangan so'rovlarni qisqa kutish,exit 1bilan chiqish. Qayta ishga tushirishni tashqi nazoratchi (Docker, systemd, PM2) qiladi.
Bu darsda
uncaughtExceptionvaunhandledRejectionqachon chiqishini aniq ayta olasiz.--unhandled-rejectionsrejimlarini (throw,strict,warn,none) farqlaysiz.- Nega xatodan keyin "ishlashda davom etish" xavfli ekanini misolda ko'rasiz.
process.exitCodevaprocess.exit()ni to'g'ri tanlaysiz.vazifalar-apigajarayon.tsni qo'shib, holat 34 ni commit qilasiz.
Oldin bilishingiz kerak: Global error handler, Ushlanmagan xatolar va global xato ushlash, process obyekti: exit kodlari va signallar, Server timeout'lari va keep-alive.
1. Nega bu kerak?
Oldingi darsda global xato ishlovchi yozdik — marshrutdagi har throw u yerga keladi. Lekin "har" so'zi aldaydi. Express faqat o'zi chaqirgan funksiyalardagi xatolarni ko'radi.
Tasavvur qiling: vazifa yaratilgandan keyin Telegram'ga bildirishnoma yuboriladi. Dasturchi uni kutmasdan ("fire-and-forget") chaqirdi:
async yarat(yangi) {
void bildirishnomaYubor(yangi); // await yo'q — javob tez ketsin
return ombor.yarat(yangi);
}Bir kuni Telegram API javob bermaydi va bildirishnomaYubor rad etiladi. Hech kim uni await qilmagan, hech kim .catch yozmagan. Mijoz 201 oladi — hammasi joyida ko'rinadi. Keyin esa butun server o'ladi va boshqa 50 ta foydalanuvchining so'rovi uziladi.
Ushlanmagan xatolar darsida brauzer va Node farqini ko'rgansiz: brauzerda sahifa ishlashda davom etadi, Node'da jarayon to'xtaydi. Bu darsda shu bilimni server uchun strategiyaga aylantiramiz.
O'xshatish: samolyotdagi dvigatel. Uchish paytida dvigatel yonib ketdi. Uchuvchi uni "o'zi tuzalar" deb kutmaydi va yangi yo'lovchi ham olmaydi. U qo'nadi, yo'lovchilarni tushiradi — keyin texnik xodimlar boshqa samolyot beradi.
2. Ikki hodisa
2.1 unhandledRejection — hech kim kutmagan Promise
Promise rad etildi va hozirgi microtask'lar tugaguncha unga ishlovchi qo'shilmadi. Node buni sezadi va unhandledRejection hodisasini chiqaradi. Ishlovchi qo'shsak, nima bo'lishini ko'ramiz:
process.on("unhandledRejection", (sabab, promise) => {
console.log("ushlandi:", sabab.message);
console.log("promise:", promise instanceof Promise);
});
function bildirishnomaYubor() {
return Promise.reject(new Error("Telegram javob bermadi"));
}
void bildirishnomaYubor(); // await ham, .catch ham yo'q
setTimeout(() => console.log("jarayon tirik"), 50);Konsolda:
ushlandi: Telegram javob bermadi
promise: true
jarayon tirikIshlovchi ikki narsa oladi: sabab (odatda Error) va rad etilgan Promise'ning o'zi. Diqqat qiling: jarayon o'lmadi. Ishlovchi qo'shish — Node'ga "men hal qildim" deyish.
2.2 Ishlovchisiz — Node 24 sukuti
Endi o'sha kod, lekin ishlovchisiz:
function bildirishnomaYubor() {
return Promise.reject(new Error("Telegram javob bermadi"));
}
console.log("so'rov qabul qilindi");
void bildirishnomaYubor();
setTimeout(() => console.log("bu chiqmaydi"), 50);Konsolda:
so'rov qabul qilindi
Error: Telegram javob bermadi"bu chiqmaydi" haqiqatan chiqmadi: jarayon taymerni kutmasdan o'ldi. Node 15 dan beri sukut — --unhandled-rejections=throw: rad etish uncaughtException ga aylanadi, u ham ushlanmasa — chiqish kodi 1. Node 14 va undan eskida bu faqat ogohlantirish edi. Eski blog maqolalarida "Node ogohlantirish beradi" deyilsa, sababi shu.
2.3 uncaughtException — sinxron xato hech kim ushlamagan
process.on("uncaughtException", (xato, kelib) => {
console.log(`ushlandi (${kelib}):`, xato.message);
});
setTimeout(() => {
throw new Error("taymer ichida");
}, 10);
Promise.reject(new Error("rad etildi"));Konsolda:
ushlandi (unhandledRejection): rad etildi
ushlandi (uncaughtException): taymer ichidaIkkinchi parametr — kelib (origin): xato qayerdan kelgani. unhandledRejection ishlovchisi yo'q bo'lsa, rad etish ham shu yerga unhandledRejection belgisi bilan tushadi. Bu — sukut throw rejimining ishi.
Express bilan bog'lasak — kanon tajribasi t34-halokat.txt (Express 5.3.0). Unda ikki marshrut bor: /a — async handler ichida throw, /t — setTimeout ichida throw. Chiqishda ikki narsaga qarang: /a ga qanday javob keldi va eng oxirgi qatordagi chiqish kodi:
8. Express 5 …: async throw — ushlanadi; setTimeout ichidagi throw — Express'dan TASHQARIDA
/a → 500
…
Error: setTimeout ichida throw
Node.js v24.21.0
[exit 1]Nima bo'ldi? async handler'dagi throw — 500 javob: Express 5 rad etilgan Promise'ni o'zi ushladi. setTimeout ichidagi throw esa handler tugagandan keyin, boshqa taymer navbatida sodir bo'ldi. U paytda Express'ning hech qanday try bloki ishlamayapti — xato uncaughtException ga aylandi va jarayon exit 1 bilan o'ldi.
Tekshirib ko'ring:
app.get("/", async () => { await Promise.reject(new Error("x")); })— buunhandledRejectionbo'ladimi?
Javob
Yo'q. Handler async, await rad etishni handler Promise'ining rad etilishiga aylantiradi. Express 5 o'sha Promise'ni kutadi va xatoni ishlovchiga uzatadi — 500. unhandledRejection faqat hech kim kutmagan Promise'da chiqadi: void f(), .then() siz f(), catch siz setTimeout(async …).
3. Rejimlar: --unhandled-rejections
Node bayroq bilan sukutni o'zgartiradi. Kanon tajribasi har rejimni alohida jarayonda sinadi (t34-halokat.txt, Node 24.21.0; kod: Promise.reject(new Error("rad")); setTimeout(() => console.log("100 ms dan keyin ham tirik"), 100);):
| Rejim | Natija | Chiqish kodi |
|---|---|---|
sukut (throw) |
stack stderr'ga, taymer ishlamadi | 1 |
strict |
xuddi shunday, ishlovchi bo'lsa ham uncaughtException |
1 |
warn |
UnhandledPromiseRejectionWarning, ishlashda davom |
0 |
none |
hech narsa chiqmadi, ishlashda davom | 0 |
Va ishlovchi bilan:
5. process.on('unhandledRejection') bor — jarayon O'LMAYDI (ishlovchi o'zi hal qilishi kerak)
exit 0; stdout: "ushlandi: rad\n100 ms dan keyin ham tirik"; stderr:warn va none — xatoni gilam ostiga supurish. Ular faqat eski kodni vaqtincha yangi Node'da ishlatish uchun. Yangi loyihada sukutdan foydalaning.
4. Nega "davom etish" xavfli
4.1 Yarim bajarilgan ish
Xato funksiya o'rtasida chiqdi. Undan oldingi qatorlar bajarildi, keyingilar — yo'q. Xotiradagi holat endi "yarim":
const hisoblar = { ali: 100_000, vali: 0 };
let jamiOtkazmalar = 0;
function otkaz(kimdan, kimga, summa) {
hisoblar[kimdan] -= summa;
jamiOtkazmalar++;
if (kimga === "sardor") throw new Error("hisob topilmadi");
hisoblar[kimga] += summa;
}
process.on("uncaughtException", (x) => {
console.log("ushlandi:", x.message, "— davom etamiz");
});
setTimeout(() => otkaz("ali", "sardor", 30_000), 0);
setTimeout(() => {
const jami = hisoblar.ali + hisoblar.vali;
console.log("hisoblar:", hisoblar, "jami:", jami);
console.log("o'tkazmalar:", jamiOtkazmalar);
}, 20);Konsolda:
ushlandi: hisob topilmadi — davom etamiz
hisoblar: { ali: 70000, vali: 0 } jami: 70000
o'tkazmalar: 130 000 so'm yo'qoldi. Jarayon "tirik", lekin uning xotirasi endi yolg'on gapiradi — va keyingi har so'rov shu yolg'onga tayanadi. Haqiqiy serverda bu yarim yangilangan kesh, yopilmagan fayl, qulf (lock) qo'yilgan va hech qachon ochilmaydigan resurs bo'ladi.
4.2 Node hujjati nima deydi
Node.js 24 hujjati uncaughtException haqida aniq yozadi: bu hodisa — oxirgi chora, uni "On Error Resume Next" sifatida ishlatmang; ushlanmagan xatodan keyin jarayon holati noma'lum, to'g'ri ishlatish — sinxron tozalash va jarayonni to'xtatish. Xavfsiz qayta tiklashning yagona ishonchli yo'li — jarayonni qayta ishga tushirish.
Shuning uchun "let it crash" ("yiqilsin") — dangasalik emas, ongli strategiya. Erlang dunyosidan kelgan g'oya: buzilgan jarayonni tuzatishga urinma, toza jarayon bilan almashtir.
4.3 Kim qayta ishga tushiradi?
Jarayonning o'zi emas — u o'lgan. Tashqaridagi nazoratchi (supervisor):
| Nazoratchi | Qayerda | Kursda |
|---|---|---|
Docker restart: unless-stopped |
Konteyner | 33-qism |
systemd Restart=on-failure |
Linux xizmati | 33-qism |
| PM2 | Node jarayon menejeri | 33-qism |
| Kubernetes | Klaster | 34-qism |
Hozir ular haqida ikki narsani bilish yetarli: ular exit kodini ko'radi va 0 bo'lmasa qayta ishga tushiradi. Shuning uchun chiqish kodi to'g'ri bo'lishi shart — xato bilan o'lgan jarayon 1 bilan chiqsin.
sequenceDiagram
participant S as Nazoratchi
participant P as Node jarayoni
participant M as Mijozlar
S->>P: ishga tushir
M->>P: so'rovlar
Note over P: unhandledRejection
P->>P: log.fatal
P-->>M: yangi ulanish yo'q
P->>M: boshlangan so'rovlar tugaydi (≤ 3 s)
P->>S: exit 1
S->>P: yangi toza jarayonDiagrammaga qarang: jarayon "o'zini davolamaydi". U faqat chiroyli o'ladi — boshqalar uni almashtiradi.
Tekshirib ko'ring: Nega 3 soniyani kutish kerak — darhol
process.exit(1)qilsa bo'lmaydimi?
Javob
Xato bitta so'rovda (yoki fon ishida) chiqdi, lekin shu payt boshqa 20 ta so'rov sog' ishlayotgan bo'lishi mumkin. Darhol exit ularni ham uzadi. Qisqa kutish ularga tugash imkonini beradi. Lekin cheksiz emas: holat noma'lum, shuning uchun 3 s dan keyin baribir chiqamiz.
5. process.exitCode va process.exit()
process darsida ikkalasini ko'rgansiz. Bu yerda farq hal qiluvchi:
process.on("exit", (kod) => console.log("chiqish kodi:", kod));
process.exitCode = 1;
setTimeout(() => console.log("navbatdagi ish bajarildi"), 10);
console.log("exitCode qo'yildi");Konsolda:
exitCode qo'yildi
navbatdagi ish bajarildi
chiqish kodi: 1process.exitCode = 1 — "tugaganda 1 bilan chiq". Event loop'dagi ishlar (javob yuborish, log yozish) tugaydi. process.exit(1) esa darhol to'xtatadi — kanon tajribasida (7b) stdout bo'sh qoldi. Shuning uchun asosiy yo'l — exitCode, exit() esa faqat zaxira: "kutish osilib qolsa ham chiq".
exit hodisasida faqat sinxron kod ishlaydi — u yerda await yoki setTimeout foydasiz, ular hech qachon bajarilmaydi.
6. Kanon: src/jarayon.ts
Holat 34 da signal va halokat kodi bitta faylga yig'ildi. To'liq fayl (kanon; qatorlar telefon uchun Prettier bilan bo'lindi, kod aynan):
// jarayon.ts — jarayon darajasidagi hodisalar: signal bilan to'xtash
// va ushlanmagan xatolar ("let it crash").
// Ushlanmagan xatodan keyin jarayon holati NOMA'LUM — davom etish
// xavfli: log → boshlangan so'rovlarni qisqa kutish → exit 1.
// Qayta ishga tushirishni supervisor qiladi (Docker restart:
// unless-stopped, systemd, PM2 — 33-qism)
import type { Server } from "node:http";
import { yop } from "./yopish.ts";
export type JarayonLogi = {
info: (xabar: string) => void;
fatal: (xabar: string, xato: unknown) => void;
};
export function jarayonniBoshqar(
server: Server,
log: JarayonLogi,
{ kutishMs = 10_000, halokatKutishMs = 3_000 } = {},
): void {
let yopilmoqda = false;
async function toxta(
sabab: string,
kod: number,
kutish: number,
): Promise<void> {
if (yopilmoqda) return;
yopilmoqda = true;
log.info(
`${sabab}: yangi ulanishlar qabul qilinmaydi, boshlanganlari tugashi kutilmoqda`,
);
const ozVaqtida = await yop(server, kutish);
log.info(
ozVaqtida
? "to'xtadi"
: `${String(kutish)} ms o'tdi — qolgan ulanishlar uzildi`,
);
process.exitCode = kod !== 0 ? kod : ozVaqtida ? 0 : 1;
}
function halokat(tur: string, xato: unknown): void {
log.fatal(tur, xato);
// Majburiy chiqish: yopish osilib qolsa ham jarayon tugasin
// (timer jarayonni ushlab turmasin — unref)
setTimeout(
() => process.exit(1),
halokatKutishMs + 1000,
).unref();
void toxta(tur, 1, halokatKutishMs);
}
// Docker/systemd — SIGTERM; terminalda Ctrl+C — SIGINT (Windows'da
// boshqa jarayondan SIGINT ishlovchini chaqirmaydi — 24-qism #08)
process.once("SIGTERM", () => void toxta("SIGTERM", 0, kutishMs));
process.once("SIGINT", () => void toxta("SIGINT", 0, kutishMs));
// Node 24 sukuti: unhandledRejection → uncaughtException → darhol
// exit 1 (log va yopishsiz). Ishlovchi qo'shsak — o'zimiz hal
// qilamiz
process.on("unhandledRejection", (sabab) => {
halokat("unhandledRejection", sabab);
});
process.on("uncaughtException", (xato, kelib) => {
halokat(`uncaughtException (${kelib})`, xato);
});
}Har qatorning sababi:
| Qator | Nima uchun |
|---|---|
yopilmoqda bayrog'i |
Ikkinchi xato yoki signal yopishni qayta boshlamasin |
yop(server, kutish) |
Timeout'lar darsidagi graceful shutdown: yangi ulanish yo'q, bo'shlarini yopadi, kutadi |
log.fatal birinchi |
Keyin nima bo'lmasin — sabab logda qoladi |
setTimeout(process.exit, …).unref() |
Zaxira: yopish osilsa ham 4 s da chiqamiz; unref — taymer o'zi jarayonni ushlab turmasin |
process.exitCode = 1 |
Tinch chiqish; nazoratchi 1 ni ko'rib qayta ishga tushiradi |
| SIGTERM → kod 0 | Rejali to'xtash (deploy) — xato emas |
process.once va process.on farqi: signal bir marta kifoya. Xatolar uchun on: yopish paytida ikkinchi xato chiqsa ham, u halokat ga tushadi (bayroq qayta yopishga yo'l qo'ymaydi) va jarayon sukut bilan qo'shimcha "o'lim" chiqarmaydi.
6.1 server.ts endi qisqa
src/server.ts — to'liq (holat 34):
// server.ts — kirish nuqtasi: ishga tushirish, jarayon hodisalari
import { ishgaTushir } from "./ishga-tushir.ts";
import { jarayonniBoshqar } from "./jarayon.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
const { server, manzil } = await ishgaTushir({
port: Number(process.env.PORT ?? 3000),
host: process.env.HOST ?? "127.0.0.1",
bogliqliklar: {
ombor: new XotiraOmbori(),
// CORS_ORIGINS="http://localhost:5173,https://login.github.io"
// (#37 da tipli config'ga o'tadi)
corsOriginlar: (process.env.CORS_ORIGINS ?? "")
.split(",")
.map((o) => o.trim())
.filter((o) => o !== ""),
},
});
console.log(`vazifalar-api: ${manzil}`);
jarayonniBoshqar(server, {
info: (x) => {
console.log(x);
},
fatal: (x, xato) => {
console.error(x, xato);
},
});Logger hozircha console. Pino darsida log.fatal({ err }) ga almashadi — jarayon.ts o'zgarmaydi, chunki u faqat JarayonLogi turini biladi.
6.2 Test: haqiqiy jarayonda
Jarayon o'limini test jarayonining ichida tekshirib bo'lmaydi — test runner ham o'ladi. Kanon bola jarayon (child_process) ishga tushiradi. Qolip test/qolip/halokat.ts (to'liq) — ombor yarat() ichida "unutilgan" Promise rad etiladi:
// halokat.ts — test qolipi (alohida jarayon): yarat() ichida
// "unutilgan" Promise rad etiladi (fire-and-forget xatosi)
import { ishgaTushir } from "../../src/ishga-tushir.ts";
import { jarayonniBoshqar } from "../../src/jarayon.ts";
import { XotiraOmbori } from "../../src/vazifalar/xotira-ombor.ts";
import type {
Vazifa,
YangiVazifa,
} from "../../src/vazifalar/vazifa.ts";
class XatoliOmbor extends XotiraOmbori {
override async yarat(y: YangiVazifa): Promise<Vazifa> {
// hech kim ushlamaydi
void Promise.reject(new Error("bildirishnoma yuborilmadi"));
await new Promise((r) => setTimeout(r, 200));
return super.yarat(y);
}
}
const { server, manzil } = await ishgaTushir({
port: 0,
bogliqliklar: { ombor: new XatoliOmbor() },
});
jarayonniBoshqar(
server,
{
info: (x) => {
console.log(x);
},
fatal: (x, xato) => {
console.error(x, xato instanceof Error ? xato.message : xato);
},
},
{ halokatKutishMs: 1000 },
);
console.log(manzil);Test (test/jarayon.test.ts, to'liq) uchta narsani tasdiqlaydi:
import { test } from "node:test";
import assert from "node:assert/strict";
import { spawn } from "node:child_process";
import { join } from "node:path";
test("unhandledRejection: log → boshlangan so'rov tugaydi → exit 1 (let it crash)", async () => {
const bola = spawn(
process.execPath,
[join(import.meta.dirname, "qolip", "halokat.ts")],
{ stdio: ["ignore", "pipe", "pipe"] },
);
let stdout = "";
let stderr = "";
bola.stderr
.setEncoding("utf8")
.on("data", (d: string) => (stderr += d));
const manzil = await new Promise<string>((resolve) => {
bola.stdout.setEncoding("utf8").on("data", (d: string) => {
stdout += d;
const m = /http:\/\/[\d.]+:\d+/.exec(stdout);
if (m) resolve(m[0]);
});
});
const javob = await fetch(`${manzil}/v1/vazifalar`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: '{"matn":"x"}',
});
// halokatdan keyin ham boshlangan so'rov javob oldi
assert.equal(javob.status, 201);
const kod = await new Promise<number | null>((r) =>
bola.on("exit", r),
);
assert.equal(kod, 1);
assert.match(
stderr,
/unhandledRejection bildirishnoma yuborilmadi/,
);
assert.match(
stdout,
/unhandledRejection: yangi ulanishlar qabul qilinmaydi/,
);
});Rad etish 200 ms lik yarat() o'rtasida chiqadi. Natija: (1) boshlangan so'rov baribir 201 oldi — graceful kutish ishladi; (2) jarayon exit 1; (3) sabab stderr'da. Bu — butun strategiyaning bitta testda isboti.
Diqqat: Haqiqiy
SIGTERMWindows'da boshqa jarayondan yuborilganda ishlovchini chaqirmaydi (process darsidagi topilma). Kanon signal yo'lini sinamagan — u Docker/Linux'da (33-qism) tekshiriladi. Halokat yo'li esa test bilan tasdiqlangan.
7. API qadami: jarayon va commit (holat 34, 6-qism)
Bugungi o'zgarishlar:
| Fayl | O'zgarish |
|---|---|
src/jarayon.ts |
Yangi: jarayonniBoshqar — SIGTERM/SIGINT, unhandledRejection, uncaughtException |
src/server.ts |
Signal kodi jarayon.ts ga ko'chdi |
test/jarayon.test.ts, test/qolip/halokat.ts |
Bola jarayonda halokat testi |
Holat 34 olti darsning yig'indisi: validatsiya, Zod 4 (+ o'zbekcha lokal), validatorlar taqqosi (tajriba, kanonga kirmadi), xato iyerarxiyasi, global ishlovchi va bugungi jarayon.ts. Tekshiring:
npm run checkKanon natijasi (tekshiruv-34.txt): tip 0 (3.4 s), lint 0 (6.9 s), testlar 36/36; prod bog'liqlik 4 (cors, express, helmet, zod) — 69 paket; sovuq start 587 ms (mashina yuklangan). Biz ham npm ci dan keyin ishga tushirdik: tests 36, pass 36.
Commit (kanon):
git add -A
git commit -m "Zod 4 bilan validatsiya (o'zbekcha lokal), xato iyerarxiyasi, global xato ishlovchi, unhandledRejection — let it crash"8. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Server "tasodifan" o'ladi, 201 dan keyin | void f() — rad etish ushlanmagan |
await yoki .catch(log) |
| Server o'lmaydi, lekin g'alati ishlaydi | uncaughtException da davom etish |
log → yopish → exit 1 |
| Xato logda yo'q | process.exit() darhol — log yozilmay qoldi |
exitCode + kutish |
| Jarayon yopilmay osilib qoldi | Ochiq ulanish yoki taymer | Zaxira setTimeout(exit).unref() |
| Nazoratchi qayta ishga tushirmaydi | Xato bilan chiqishda kod 0 | exitCode = 1 |
| Eski kod "faqat ogohlantirish" kutadi | Node 15+ sukuti throw |
Rad etishlarni ushlang; warn — vaqtinchalik |
9. Mashqlar
Yechimlar kurs/mashqlar/25/34-jarayon-xatolari/ da.
1-mashq (oson): o'ladimi?
Ishlovchi yo'q, Node 24 sukuti. Jarayon o'ladimi?
setTimeout(() => { throw new Error("x"); })—async function f() { throw new Error("x"); } f().catch(() => {});—new Promise((_, rad) => rad(new Error("x")));—try { await Promise.reject(new Error("x")); } catch {}—
Yechim
- ha — taymer ichidagi sinxron
throw, hech kim ushlamaydi. - yo'q —
.catchrad etishni ushladi. - ha — rad etilgan Promise'ga ishlovchi qo'shilmagan.
- yo'q —
awaitrad etishnithrowga aylantirdi,catchushladi.
2-mashq (o'rta): xavfsizFon
Fire-and-forget ishlar uchun xavfsizFon(nom, fn) yozing: fn() ni chaqiradi, lekin uni kutmaydi; rad etilsa — console.log("fon xatosi [nom]: …") va jarayon tirik qoladi. Ichida process.on ishlatmang.
Yechim
function xavfsizFon(nom, fn) {
Promise.resolve()
.then(fn)
.catch((x) => {
const xabar = x instanceof Error ? x.message : String(x);
console.log(`fon xatosi [${nom}]: ${xabar}`);
});
}
xavfsizFon("telegram", async () => {
throw new Error("javob bermadi");
});
xavfsizFon("sinxron", () => {
throw new Error("darhol yiqildi");
});
xavfsizFon("yaxshi", async () => console.log("yuborildi"));
setTimeout(() => console.log("jarayon tirik"), 20);Konsolda:
yuborildi
fon xatosi [sinxron]: darhol yiqildi
fon xatosi [telegram]: javob bermadi
jarayon tirikPromise.resolve().then(fn) — fn sinxron throw qilsa ham, u rad etishga aylanadi va .catch ga tushadi. Tartibga qarang: uchala fn ham bitta microtask navbatida ishlaydi, shuning uchun "yuborildi" birinchi. Sinxron throw darhol rad etishga aylanadi, async funksiyaning rad etilgan Promise'i esa then ichida yana bir necha qadam "ochiladi" — shuning uchun telegram oxirroq. Kanondagi webhook (#49) ham shunday ishlaydi: xato warn log, API javobi buzilmaydi.
3-mashq (qiyin): kichik "nazoratchi"
spawnSync bilan bola jarayonni ishga tushiradigan nazoratchi(kod, urinishlar) yozing. Bola exit 0 qilsa — to'xtaydi; 0 bo'lmasa — qayta ishga tushiradi, ko'pi bilan urinishlar marta. Bola kodi — 3-ishga tushirishda muvaffaqiyatli bo'ladigan dastur (process.env.URINISH orqali raqamini biladi).
Yechim
import { spawnSync } from "node:child_process";
function nazoratchi(kod, urinishlar) {
for (let i = 1; i <= urinishlar; i++) {
const r = spawnSync(process.execPath, ["-e", kod], {
env: { ...process.env, URINISH: String(i) },
encoding: "utf8",
});
console.log(`${i}-urinish: exit ${r.status}`);
if (r.status === 0) return true;
}
console.log("taslim — odamni chaqiring");
return false;
}
const bola = `
if (Number(process.env.URINISH) < 3) {
setTimeout(() => { throw new Error("buzildi"); });
}
`;
console.log(nazoratchi(bola, 5));
console.log(nazoratchi("process.exitCode = 1", 2));Konsolda:
1-urinish: exit 1
2-urinish: exit 1
3-urinish: exit 0
true
1-urinish: exit 1
2-urinish: exit 1
taslim — odamni chaqiring
falseHaqiqiy nazoratchilar (systemd, PM2) yana ikki narsa qo'shadi: urinishlar orasida kutish (sekin-sekin oshadi) va "qisqa vaqtda juda ko'p yiqildi — to'xtat" chegarasi. Aks holda doim yiqiladigan dastur CPU'ni cheksiz qayta ishga tushirish bilan band qiladi. Config xatosi uchun alohida exit kod — tipli config darsida.
4-mashq: API qadami
src/jarayon.ts ni yozing va server.ts ni unga o'tkazing. test/qolip/halokat.ts va test/jarayon.test.ts ni qo'shing: bola jarayonda POST → 201, keyin exit 1, stderr'da sabab. npm run check (36/36) va holat 34 ni kanon xabari bilan commit qiling.
Yechim
Kod — «Kanon» bo'limidagi to'liq jarayon.ts, server.ts, qolip va test fayllari. Testda bola jarayon manzilini stdout'dan regex bilan oling (/http:\/\/[\d.]+:\d+/) — qolip port: 0 bilan ishga tushadi va manzilni chop etadi. Halokat kutishini testda qisqartiring (halokatKutishMs: 1000), aks holda test 4 s kutadi. Commit xabari — «API qadami» bo'limida.
10. Real ishda
- Har prod Node xizmatida shu fayl bor: signal ishlovchisi + halokat ishlovchisi + tashqi nazoratchi. Docker'da
restartsiyosatisiz konteyner birinchi xatoda abadiy o'lib qoladi (33-qism). - Monitoring xizmatlari (Sentry, 34-qism)
uncaughtExceptionga o'z ishlovchisini qo'yadi — hisobot yuborib, keyin jarayonni o'ldiradi. Ular ham "davom etish" ni tavsiya qilmaydi. - Kodni ko'rib chiqishda (code review)
void f(),.then()siz Promise vaasynccallback (forEach(async …),setTimeout(async …)) — qizil bayroq. - Intervyu savollari: "
uncaughtExceptiondan keyin ishlashda davom etsa bo'ladimi?", "Node 15 da rad etishlar bilan nima o'zgardi?", "process.exitvaprocess.exitCodefarqi?"
Xulosa
- Express faqat o'zi chaqirgan kodning xatosini ko'radi; taymer va "unutilgan" Promise — jarayon darajasiga chiqadi.
- Node 24 sukuti: ushlanmagan rad etish →
uncaughtException→exit 1.warn/none— faqat vaqtinchalik. - Ishlovchi qo'shish = "men hal qildim": jarayon o'lmaydi, lekin holat noma'lum bo'lib qoladi.
- "Let it crash":
log.fatal→ yangi ulanishlarni to'xtatish → ≤ 3 s kutish →exitCode = 1; zaxirasetTimeout(exit).unref(). - Qayta ishga tushirish — tashqi nazoratchining ishi (Docker, systemd, PM2).
Keyingi dars: Environment o'zgaruvchilar va .env — vazifalar-api sozlamalarini koddan muhitga chiqaramiz: 12-factor, NODE_ENV, .env.example.
Manbalar
- Node.js 24 hujjati — Process:
uncaughtException("Warning: Using uncaughtException correctly"),unhandledRejection,process.exitCode: https://nodejs.org/docs/latest-v24.x/api/process.html - Node.js 24 hujjati — CLI:
--unhandled-rejections: https://nodejs.org/docs/latest-v24.x/api/cli.html#--unhandled-rejectionsmode - Node.js 15 reliz e'loni (sukut
throwga o'zgardi, 2020-10-20): https://nodejs.org/en/blog/release/v15.0.0 - Joyent — Error Handling in Node.js: https://www.joyent.com/node-js/production/design/errors
- Kanon:
vazifalar-apiholat 34 (src/jarayon.ts,test/jarayon.test.ts,test/qolip/halokat.ts),natijalar/tajriba/t34-halokat.txt,natijalar/tekshiruv-34.txt. Testlar 36/36 — biz ham ishga tushirdik (Node 24.21.0, Windows 11).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!