IlmHamroh
JavaScript Full-stack/25-qism. HTTP, Express va REST API34/51-dars18 daqiqa
Mundarija (25)

Node.js process darajasidagi xatolar: uncaughtException, unhandledRejection va "let it crash"

Qisqacha: Express ushlay olmagan xato (taymer ichidagi throw, hech kim await qilmagan rad etilgan Promise) jarayon darajasiga chiqadi. Node 24 sukuti: jarayon darhol exit 1 bilan 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 1 bilan chiqish. Qayta ishga tushirishni tashqi nazoratchi (Docker, systemd, PM2) qiladi.

Bu darsda

  • uncaughtException va unhandledRejection qachon chiqishini aniq ayta olasiz.
  • --unhandled-rejections rejimlarini (throw, strict, warn, none) farqlaysiz.
  • Nega xatodan keyin "ishlashda davom etish" xavfli ekanini misolda ko'rasiz.
  • process.exitCode va process.exit() ni to'g'ri tanlaysiz.
  • vazifalar-api ga jarayon.ts ni 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:

ts
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:

js
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:

text
ushlandi: Telegram javob bermadi
promise: true
jarayon tirik

Ishlovchi 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:

js
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:

text
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

js
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:

text
ushlandi (unhandledRejection): rad etildi
ushlandi (uncaughtException): taymer ichida

Ikkinchi 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:

text
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")); }) — bu unhandledRejection bo'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:

text
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":

js
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:

text
ushlandi: hisob topilmadi — davom etamiz
hisoblar: { ali: 70000, vali: 0 } jami: 70000
o'tkazmalar: 1

30 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 jarayon

Diagrammaga 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:

js
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:

text
exitCode qo'yildi
navbatdagi ish bajarildi
chiqish kodi: 1

process.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):

ts
// 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):

ts
// 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:

ts
// 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:

ts
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 SIGTERM Windows'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:

bash
npm run check

Kanon 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):

bash
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 — .catch rad etishni ushladi.
  • ha — rad etilgan Promise'ga ishlovchi qo'shilmagan.
  • yo'q — await rad etishni throw ga aylantirdi, catch ushladi.

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
js
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:

text
yuborildi
fon xatosi [sinxron]: darhol yiqildi
fon xatosi [telegram]: javob bermadi
jarayon tirik

Promise.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
js
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:

text
1-urinish: exit 1
2-urinish: exit 1
3-urinish: exit 0
true
1-urinish: exit 1
2-urinish: exit 1
taslim — odamni chaqiring
false

Haqiqiy 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 restart siyosatisiz konteyner birinchi xatoda abadiy o'lib qoladi (33-qism).
  • Monitoring xizmatlari (Sentry, 34-qism) uncaughtException ga 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 va async callback (forEach(async …), setTimeout(async …)) — qizil bayroq.
  • Intervyu savollari: "uncaughtException dan keyin ishlashda davom etsa bo'ladimi?", "Node 15 da rad etishlar bilan nima o'zgardi?", "process.exit va process.exitCode farqi?"

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; zaxira setTimeout(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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js process darajasidagi xatolar: uncaughtException, unhandledRejection va "let it crash" — IlmHamroh