Mundarija (34)
- Bu darsda
- 1. Nega bu kerak?
- 2. Ishlovchi bo'lmasa nima bo'ladi?
- 2.1 Biz o'lchadik: Express'ning sukut ishlovchisi
- 3. To'rt argument: Express xato ishlovchini qanday taniydi
- 3.1 Qoida — fn.length
- 3.2 Model: zanjir xatoni qanday o'tkazadi
- 3.3 Haqiqiy Express'da 3 argumentli "ishlovchi"
- 4. Xato zanjirga qanday tushadi
- 4.1 To'rt yo'l
- 4.2 next ga nima berilsa — xato?
- 4.3 Ishlovchi ichidagi xato
- 5. Tartib: 404 va ishlovchi eng oxirida
- 5.1 Biz tekshirdik: ishlovchi marshrutdan oldin
- 5.2 404 ham xato
- 6. Kanon: src/umumiy/xato-ishlovchi.ts
- 6.1 Uch tarmoq
- 6.2 Ikkinchi tarmoq: begona xatolarni tanish
- 6.3 Uchinchi tarmoq: 500 va yashirish
- 6.4 headersSent: javob yarmida xato
- 7. Boshqa xatolarni map qilish
- 7.1 Validatsiya — allaqachon tayyor
- 7.2 Baza xatolari — 26-qismda
- 8. Hujumchi nigohi
- 9. API qadami: global xato ishlovchi (holat 34, 5-qism)
- 10. Ko'p uchraydigan xatolar
- 11. Mashqlar
- 1-mashq (oson): qaysi biri xato ishlovchi?
- 2-mashq (o'rta): modelga 404 qo'shing
- 3-mashq (qiyin): problemga(xato, sorovId)
- 4-mashq: API qadami
- 12. Real ishda
- Xulosa
- Manbalar
Express global error handler: 4 argumentli middleware va 404 ishlovchi
Qisqacha: Express xato ishlovchini argumentlar soni bo'yicha taniydi:
(err, req, res, next)— to'rtta. Uchta yozsangiz, u oddiy middleware bo'lib qoladi va xatoni hech qachon ko'rmaydi. Ishlovchi barcha marshrutlardan va 404 ishlovchidan keyin turadi. Uning vazifasi: o'z xatolarimizni Problem Details'ga aylantirish, kutubxona xatolarini (yaroqsiz JSON, 413) tanish, qolganiga 500 va umumiy matn berish — stack va asl xabar faqat logga. O'z ishlovchingiz bo'lmasa, Express'ning sukut ishlovchisi development'da stack'ni mijozga HTML qilib ko'rsatadi — biz buni o'lchadik.
Bu darsda
- 4 argumentli middleware'ni yozasiz va Express uni qanday tanishini tushuntira olasiz.
next(err),throw(sinxron va async) vanext('route')farqini bilasiz.- 404 ishlovchini to'g'ri joyga qo'yasiz.
- body-parser va serve-static xatolarini (
http-errors) tanib, o'zbekcha javobga aylantirasiz. - 500 da stack va ichki xabarni yashirib, logga
sorovIdbilan yozasiz;headersSentholatini to'g'ri hal qilasiz.
Oldin bilishingiz kerak: Xato turlari va custom error iyerarxiyasi, Middleware va request lifecycle, Express 5: async xatolar, Xato javoblari: Problem Details.
1. Nega bu kerak?
Oldingi darsda xatolarni turlarga ajratdik: TopilmadiXatosi, ValidatsiyaXatosi va boshqalar. Ular throw qilinadi — lekin keyin nima bo'ladi? Kimdir ularni ushlab, javobga aylantirishi kerak.
Har marshrutda try/catch yozish mumkin. 15 ta marshrut — 15 ta bir xil catch bloki. Bittasida sorovId unutiladi, boshqasida stack javobga tushib qoladi. Bunday kodni kim tekshiradi?
To'g'ri yo'l — bitta joy. Hamma xato oxir-oqibat o'sha yerga oqib keladi, u yerda bir xil qoida bilan javob yasaladi va bir xil formatda log yoziladi. Express'da bu joy — global xato ishlovchi (global error handler).
O'xshatish: kasalxonaning qabul bo'limi. Qaysi qavatda nima bo'lmasin — bemor bitta bo'limga keladi. U yerda navbatchi shifokor tashxis qo'yadi: "yengil — uyga", "og'ir — reanimatsiya". Har qavatda alohida tez yordam bo'lmaydi.
2. Ishlovchi bo'lmasa nima bo'ladi?
2.1 Biz o'lchadik: Express'ning sukut ishlovchisi
Express 5.3.0 da hech qanday xato ishlovchisiz marshrut yozdik:
import express from "express";
const app = express();
app.get("/", () => {
throw new Error("maxfiy: parol=123");
});Biz ishga tushirdik (Node 24.21.0, NODE_ENV berilmagan) — fetch javobi:
500 text/html; charset=utf-8 | <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Error: maxfiy: parol=123<br> at file:///C:/Users/…Mijoz uch narsani ko'rdi: xato matni (parol=123), fayl yo'li (C:/Users/… — kompyuter foydalanuvchi nomi ham) va stack — qaysi kutubxona, qaysi versiya, qaysi qator. Hujumchi uchun bu xarita.
Xuddi shu kodni NODE_ENV=production bilan:
500 text/html; charset=utf-8 <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8"> <title>Error</title> </head> <body> <pre>Internal Server Error</pre> </body> </html>Production'da yashirildi. Lekin uchta muammo qoldi: javob HTML (bizning API JSON va application/problem+json va'da qilgan), sorovId yo'q, va NODE_ENV ni unutish — butun himoyani o'chiradi. Ikkala holatda ham Express stack'ni server terminaliga (stderr) yozdi.
Diqqat: "Production'da yashiriladi" — himoya emas, tasodif.
NODE_ENVenv darsida ko'rasiz — u bir harf xato bilan (prod) jim o'chib qoladi.
3. To'rt argument: Express xato ishlovchini qanday taniydi
3.1 Qoida — fn.length
JavaScript'da har funksiyaning length xususiyati bor — u e'lon qilingan parametrlar soni. Express middleware qo'shilganda shunga qaraydi: 4 ta — xato ishlovchi, aks holda — oddiy.
const oddiy = (req, res, next) => {};
const xatoUchun = (err, req, res, next) => {};
const unutilgan = (err, req, res) => {};
console.log(oddiy.length, xatoUchun.length, unutilgan.length);
// 3 4 3Konsolda:
3 4 3Uchinchisiga qarang: nomi err bo'lsa ham, Express uchun u oddiy middleware. Parametr nomi ahamiyatsiz, faqat soni.
3.2 Model: zanjir xatoni qanday o'tkazadi
Express'ning qoidasini 25 qatorlik modelda ko'ramiz. Bu Express emas — uning mantiqining soddalashtirilgan nusxasi, brauzerda ishlaydi:
function zanjir(qatlamlar) {
return (sorov) => {
const javob = { holat: 200, tana: "" };
let i = 0;
let xato;
function next(x) {
if (x !== undefined) xato = x;
const q = qatlamlar[i++];
if (!q) return;
const xatoUchun = q.length === 4;
// tur mos kelmasa — o'tkazib yubor
if (Boolean(xato) !== xatoUchun) return next();
try {
if (xato) q(xato, sorov, javob, next);
else q(sorov, javob, next);
} catch (e) {
next(e);
}
}
next();
return javob;
};
}
const ilova = zanjir([
(req, res, next) => { console.log("1. log"); next(); },
(req, res, next) => { throw new Error("buzildi"); },
(req, res, next) => { console.log("3. bu ishlamaydi"); },
(err, req, res, next) => {
console.log("4. ushlandi:", err.message);
res.holat = 500;
},
]);
console.log(ilova({}));Konsolda:
1. log
4. ushlandi: buzildi
{ holat: 500, tana: '' }Qoida bitta qatorda: xato bo'lsa — faqat 4 argumentlilar, bo'lmasa — faqat 3 argumentlilar. Uchinchi qatlam o'tkazib yuborildi, chunki zanjirda xato bor edi.
3.3 Haqiqiy Express'da 3 argumentli "ishlovchi"
Endi haqiqiy Express 5.3.0 da tekshirdik (biz ishga tushirdik, t33):
app.get("/", () => { throw new Error("buzildi"); });
app.use((err, req, res) => { // ❌ 3 ta argument
res.status(500).json({ ushlandi: err.message });
});1. (err, req, res) — 3 argument: 500 text/html; charset=utf-8 | <!DOCTYPE html> … <pre>Error: buzildi<br> at file:///C:/Users/…Bizning "ishlovchi" chaqirilmadi — javob Express'ning HTML'i, stack bilan. Yana yomoni: xatosiz so'rovda u oddiy middleware bo'lib chaqiriladi. err o'rniga so'rov, res o'rniga next funksiyasi keladi:
chaqirildi! err = IncomingMessage | res = functionBu yerda res.status(...) — next.status(...), ya'ni TypeError. TypeScript bunday holatda yordam beradi: kanon ErrorRequestHandler turi bilan yozadi va next ni ishlatmasa ham ro'yxatda qoldiradi.
Tekshirib ko'ring: ESLint
no-unused-varsqoidasi "nextishlatilmagan" deb ogohlantirdi. Uni o'chirib tashlasangiz nima bo'ladi?
Javob
Funksiya 3 argumentli bo'lib qoladi va Express uni oddiy middleware deb hisoblaydi — xato ishlovchi butunlay ishlamay qoladi. To'g'ri yo'l: next ni qoldirish va ogohlantirishni o'chirish (_next nomi bilan yoki ESLint izohi bilan) yoki next ni haqiqatan ishlatish (kanondagi headersSent tarmog'i aynan next(xato) chaqiradi).
4. Xato zanjirga qanday tushadi
4.1 To'rt yo'l
| Kod | Ishlovchiga yetadimi? |
|---|---|
Sinxron handler'da throw |
Ha — Express try/catch ichida chaqiradi |
async handler'da throw yoki rad etilgan await |
Ha — Express 5 da (Express 5 darsi); Express 4 da so'rov osilib qolardi |
next(xato) |
Ha — callback kodidan xato uzatishning yo'li |
setTimeout yoki callback ichida throw |
Yo'q — Express'dan tashqarida; jarayon o'ladi |
Oxirgi qatorni kanon tajribasi (t34-halokat.txt) tasdiqladi: setTimeout(() => { throw … }) — Express ushlamadi, jarayon exit 1 bilan tugadi. Bu keyingi darsning mavzusi.
4.2 next ga nima berilsa — xato?
next() — keyingisiga o'tish. next(x) — x xato. Ikkita istisno bor: next("route") va next("router") — ular xato emas, middleware darsida ko'rgan "keyingi marshrutga o'tish" buyruqlari. Biz tekshirdik:
6. next('route'): 200 text/html; charset=utf-8 | keyingi routeYana bir tuzoq — Error bo'lmagan qiymat tashlash: throw "satr xato". Ishlovchiga keladi, lekin unda message ham, stack ham yo'q:
7. throw 'satr': 500 application/json; charset=utf-8 | {"tur":"string","instanceofError":false}Shuning uchun ishlovchi parametrni unknown deb oladi va avval turini tekshiradi.
4.3 Ishlovchi ichidagi xato
Ishlovchining o'zi throw qilsa-chi? Express keyingi 4 argumentli ishlovchiga o'tadi:
4. ishlovchi ichida throw: 500 application/json; charset=utf-8 | {"ikkinchi":"ishlovchi ichida: birinchi"}Keyingisi bo'lmasa — yana sukut HTML ishlovchi. Xulosa: xato ishlovchi iloji boricha oddiy bo'lsin va o'zi xato qilmasin.
5. Tartib: 404 va ishlovchi eng oxirida
5.1 Biz tekshirdik: ishlovchi marshrutdan oldin
app.use((err, req, res, next) => { res.status(500).json({}); });
app.get("/", () => { throw new Error("kech qoldi"); });3. ishlovchi oldin: 500 text/html; charset=utf-8 | <!DOCTYPE html> … <pre>Error: kech qoldi<br> …Zanjir yuqoridan pastga yuradi. Xato marshrutda paydo bo'lganda, ishlovchi allaqachon orqada qolgan. Express faqat keyingi qatlamlarga qaraydi.
5.2 404 ham xato
Hech bir marshrut mos kelmasa, Express o'zining HTML "Cannot GET /yoq" sahifasini beradi. Bizga JSON kerak. Yechim — marshrutlardan keyin oddiy middleware, u faqat bitta ish qiladi:
/** Hech bir marshrut mos kelmadi — routerlardan keyin */
export const topilmadi: RequestHandler = (req) => {
throw new TopilmadiXatosi(
`${req.method} ${req.path} — bunday manzil yo'q`);
};U javob yozmaydi — xato tashlaydi va ishni ishlovchiga topshiradi. TopilmadiXatosi ga kod berilmagan, shuning uchun type: "about:blank". Kanon ilova.ts dagi tartib:
flowchart TD
A[sorovId, helmet, cors] --> B[static, json]
B --> C["/v1 routerlar"]
C --> D[eskirgan yo'llar]
D --> E[topilmadi — 404]
E --> F[xatoIshlovchi]
C -. throw .-> F
B -. 400, 413 .-> FDiagrammada uzuq chiziqlar — xato bilan "sakrash": qayerda bo'lmasin, to'g'ridan xatoIshlovchi ga.
Bitta so'rovni boshidan oxirigacha kuzataylik: GET /v1/vazifalar/77, bunday vazifa yo'q.
| Qadam | Middleware | Natija |
|---|---|---|
| 1 | sorovId |
X-Request-Id berildi, next() |
| 2 | helmet, cors |
xavfsizlik sarlavhalari qo'yildi, next() |
| 3 | express.static |
public/ da bunday fayl yo'q — next() |
| 4 | express.json() |
GET, tana yo'q — next() |
| 5 | idempotentlik |
faqat POST'da ishlaydi — next() |
| 6 | /v1/vazifalar router: tekshirib |
params.id = 77, to'g'ri |
| 7 | handler → xizmat.ol(77) |
TopilmadiXatosi tashlandi |
| 8 | eskirgan yo'llar, topilmadi |
chetlab o'tildi — xato bor, oddiy middleware chaqirilmaydi |
| 9 | xatoIshlovchi |
404 Problem Details, kod: "vazifa-topilmadi" |
8-qadamga e'tibor bering. Xato tashlangandan keyin Express 3 argumentli middleware'larni o'tkazib yuboradi. U birinchi 4 argumentli ishlovchini qidiradi. GET /yoq esa boshqa yo'ldan yuradi: hech bir router mos kelmaydi, so'rov 8-qadamdagi topilmadi ga yetadi va u TopilmadiXatosi tashlaydi — natija yana 9-qadamda, lekin type: "about:blank" bilan.
Tekshirib ko'ring:
topilmadini/v1routerdan oldin qo'ysangiz nima bo'ladi?
Javob
Har so'rov 404 oladi — hatto GET /v1/vazifalar ham. topilmadi hech qanday shart tekshirmaydi, u o'ziga yetib kelgan har so'rovga xato tashlaydi. U ishlashi uchun undan oldingi hamma marshrutlar "imkoniyatini" olgan bo'lishi kerak.
6. Kanon: src/umumiy/xato-ishlovchi.ts
6.1 Uch tarmoq
Holat 34 da ishlovchi ilova.ts dan alohida faylga chiqdi. To'liq fayl (kanon; qatorlar telefon uchun Prettier bilan bo'lindi, kod aynan):
// xato-ishlovchi.ts — global xato ishlovchi (4 argumentli middleware,
// eng oxirida): hamma xato bir joyda Problem Details'ga aylanadi
import type { ErrorRequestHandler, RequestHandler } from "express";
import { IlovaXatosi, TopilmadiXatosi } from "./xatolar.ts";
import { problemYasa, problemYubor } from "./problem.ts";
// body-parser xatolari (`type` maydoni) → o'zbekcha tafsilot
const TANA_XATOLARI: Record<string, string> = {
"entity.parse.failed": "Tana yaroqli JSON emas",
"entity.too.large": "Tana 16 KB dan katta",
"encoding.unsupported": "Kodlash qo'llab-quvvatlanmaydi",
"charset.unsupported": "Belgilar to'plami qo'llab-quvvatlanmaydi",
};
/** http-errors (body-parser, serve-static): status + expose */
function httpErrorsmi(
x: unknown,
): x is Error & {
status: number;
expose: boolean;
type?: unknown;
} {
return (
x instanceof Error &&
"status" in x &&
typeof x.status === "number" &&
"expose" in x &&
typeof x.expose === "boolean"
);
}
/** Hech bir marshrut mos kelmadi — routerlardan keyin */
export const topilmadi: RequestHandler = (req) => {
throw new TopilmadiXatosi(
`${req.method} ${req.path} — bunday manzil yo'q`,
);
};
export type XatoLogi = (sorovId: string, xato: unknown) => void;
export function xatoIshlovchi(
logla: XatoLogi = (id, x) => {
console.error(`[${id}]`, x);
},
): ErrorRequestHandler {
return (xato: unknown, req, res, next) => {
if (res.headersSent) {
// javob yarmida — status o'zgartirib bo'lmaydi; Express o'zi
// ulanishni uzadi
logla(req.sorovId, xato);
next(xato);
return;
}
const umumiy = {
instance: req.originalUrl,
sorovId: req.sorovId,
};
if (xato instanceof IlovaXatosi) {
res.set(xato.httpSarlavhalar);
problemYubor(
res,
problemYasa(xato.holat, {
...umumiy,
kod: xato.kod,
sarlavha: xato.sarlavha,
detail: xato.message,
xatolar: xato.xatolar,
}),
);
return;
}
if (httpErrorsmi(xato) && xato.status < 500 && xato.expose) {
const tur = typeof xato.type === "string" ? xato.type : "";
problemYubor(
res,
problemYasa(xato.status, {
...umumiy,
detail: TANA_XATOLARI[tur] ?? xato.message,
}),
);
return;
}
// Kutilmagan (dasturchi xatosi, DB uzilishi…): tafsilot (stack,
// xabar) mijozga BERILMAYDI — faqat logga
logla(req.sorovId, xato);
problemYubor(
res,
problemYasa(500, {
...umumiy,
detail: "Kutilmagan xato. sorovId bilan murojaat qiling.",
}),
);
};
}logla — parametr. Sukut — console.error; testda — xotiraga yozadigan funksiya; Pino darsida — haqiqiy logger. Ishlovchi qaysi logger ishlatilishini bilmaydi.
6.2 Ikkinchi tarmoq: begona xatolarni tanish
express.json() ichidagi body-parser va express.static ichidagi serve-static o'z xatolarini http-errors kutubxonasi bilan yaratadi. Ularda status (400, 413, 415, 416) va expose (mijozga ko'rsatsa bo'ladimi) maydonlari bor. Kanon ularni oldingi darsdagi "maydon bo'yicha tekshiruv" bilan taniydi — fayldagi httpErrorsmi; type bo'yicha o'zbekcha matn — TANA_XATOLARI.
x is … — TypeScript type guard: true qaytsa, keyingi kodda x.status ishlatish mumkin. status < 500 — begona kutubxonaning 5xx xatosi ham yashiriladi.
Bu tarmoq Express'ga kirishda topilgan haqiqiy xatoning davosi: holat 13 da Range: bytes=99999- so'roviga serve-static 416 tashlagan, ishlovchi esa uni tanimay 500 qaytargan edi.
Biz holat 34 ni ishga tushirib tekshirdik (Node 24.21.0):
$ curl -X POST $A/v1/vazifalar -H 'Content-Type: application/json' -d '{"matn": "Non'
{"type":"about:blank","title":"Bad Request","status":400,"detail":"Tana yaroqli JSON emas","instance":"/v1/vazifalar","sorovId":"d95a828a-2c1a-46c9-bd3b-0961afc5a082"}
$ curl -X POST $A/v1/vazifalar … (17 000 belgili matn)
{"type":"about:blank","title":"Payload Too Large","status":413,"detail":"Tana 16 KB dan katta","instance":"/v1/vazifalar","sorovId":"91e520aa-c885-46a7-914f-85345b45c991"}
$ curl $A/yoq?x=1
{"type":"about:blank","title":"Not Found","status":404,"detail":"GET /yoq — bunday manzil yo'q","instance":"/yoq?x=1","sorovId":"92e69b08-7ce1-427e-b673-74d6c48dd661"}Uchalasi ham bir xil shaklda: type, title, status, o'zbekcha detail, instance, sorovId. Mijoz kodi (frontend) faqat bitta formatni o'qiydi.
6.3 Uchinchi tarmoq: 500 va yashirish
Biz holat 34 ga "buzuq ombor" ulab tekshirdik — ol() har safar Error("ulanish uzildi: parol=maxfiy") tashlaydi (kanon testidagi BuzuqOmbor bilan bir xil):
LOG [sinov-500-abcdef] Error: ulanish uzildi: parol=maxfiy
500 application/problem+json; charset=utf-8
{"type":"about:blank","title":"Internal Server Error","status":500,"detail":"Kutilmagan xato. sorovId bilan murojaat qiling.","instance":"/v1/vazifalar/1","sorovId":"sinov-500-abcdef"}Birinchi qator — server logi, qolgani — mijoz javobi. Ular sorovId bilan bog'langan: foydalanuvchi "sinov-500-abcdef" ni aytadi, siz logdan aynan o'sha xatoni topasiz. Javobda parol, maxfiy, fayl yo'li, stack — hech biri yo'q.
Kanon buni test bilan mustahkamlagan (test/xato.test.ts):
test("500: ichki xabar mijozga chiqmaydi, logda bor", async () => {
const r = await fetch(`${asos}/v1/vazifalar/1`,
{ headers: { "X-Request-Id": "test-500-abcdef" } });
assert.equal(r.status, 500);
const matn = await r.text();
assert.doesNotMatch(matn,
/parol|maxfiy|ulanish uzildi|BuzuqOmbor|\.ts:\d+/);
assert.ok(loglar.some((q) => q[0] === "[test-500-abcdef]"));
});doesNotMatch — "javobda bu so'zlar yo'q" tekshiruvi. Bir kun kimdir ishlovchini "yaxshilab", detail: xato.message qo'shsa — test darhol qizaradi.
6.4 headersSent: javob yarmida xato
Ba'zan javobning bir qismi allaqachon ketgan bo'ladi — masalan, eksport yoki oqim (stream) javobida. Status qatori va sarlavhalar yuborilgan, endi 500 qo'yib bo'lmaydi. Biz tekshirdik: handler res.write(...) dan keyin next(xato) chaqirdi.
headersSent: true
5. headersSent → next(err): fetch xato — terminated (UND_ERR_SOCKET)Kanon ishlovchi bu holatda logga yozadi va next(xato) bilan Express'ning sukut ishlovchisiga beradi. U ulanishni uzadi — mijoz "terminated" oladi. Bu to'g'ri: yarim javobni "muvaffaqiyatli" deb qabul qilishdan ko'ra, uzilgan ulanish yaxshiroq.
Tekshirib ko'ring:
headersSenttarmog'idares.status(500).json(...)chaqirsangiz nima bo'ladi?
Javob
ERR_HTTP_HEADERS_SENT xatosi: sarlavhalar allaqachon yuborilgan, ularni qayta yozib bo'lmaydi. Bu xato ishlovchi ichida yangi xato chiqaradi. Kanon tajribasida (t10-req-res.txt) ikkinchi res.json() aynan shu xatoni berdi.
7. Boshqa xatolarni map qilish
7.1 Validatsiya — allaqachon tayyor
Zod darsidagi tekshir() Zod xatosini o'zi ValidatsiyaXatosi ga aylantiradi. Ishlovchi Zod'ni umuman bilmaydi. Bu yaxshi chegara: validatsiya kutubxonasini almashtirsangiz, ishlovchi o'zgarmaydi.
7.2 Baza xatolari — 26-qismda
SQL qismida baza ham o'z xatolarini beradi. Masalan, Postgres takrorlanmas qiymat buzilsa, code: "23505" qaytaradi — bu 409 bo'lishi kerak. Map qilish joyi — ishlovchi emas, ombor: u bazaning xatosini KonfliktXatosi ga aylantiradi. Shunda xizmat ham, ishlovchi ham bazani bilmaydi. Hozircha g'oya yetarli — kod 26-qismda.
8. Hujumchi nigohi
- Razvedka. Hujumchi ataylab buzuq so'rovlar yuboradi:
{"matn":, ulkan tana,?limit=-1,/v1/vazifalar/%00. Maqsad — stack, kutubxona versiyasi, fayl yo'li. Bizning himoya: iyerarxiyadan tashqaridagi har xato — umumiy matn;x-powered-byo'chirilgan. - Xabardagi sirlar. DB drayveri xato xabariga ulanish satrini (parol bilan) qo'shishi mumkin.
detailga faqatIlovaXatosining o'z xabari tushadi — test buni qo'riqlaydi. - Log zaharlash. Mijoz
X-Request-Idga yangi qator belgisi yoki 10 KB matn yuborsa-chi? KanonsorovIdfaqat/^[\w.-]{8,128}$/ga mos bo'lsa qabul qiladi, aks holda yangi UUID yasaydi (Express + TypeScript darsi). - Xatolar orqali sanash. "Bunday foydalanuvchi yo'q" va "parol noto'g'ri" ni farqlash — hujumchiga qaysi email ro'yxatda ekanini aytadi. Auth 28-qismda — u yerda bitta umumiy xabar ishlatiladi.
9. API qadami: global xato ishlovchi (holat 34, 5-qism)
Hali commit qilmang — commit keyingi darsda.
| Fayl | O'zgarish |
|---|---|
src/umumiy/xato-ishlovchi.ts |
Yangi: topilmadi, xatoIshlovchi(logla), httpErrorsmi, TANA_XATOLARI |
src/ilova.ts |
Ichki ishlovchi o'chdi; app.use(topilmadi) va app.use(xatoIshlovchi(xatoLogi)) |
src/ilova.ts → Bogliqliklar |
Yangi ixtiyoriy maydon xatoLogi?: XatoLogi |
test/xato.test.ts |
Problem Details shakli (deepEqual), about:blank 404/405/415, 500 yashirish |
src/ilova.ts — to'liq (kanon holat 34; Prettier bilan bo'lingan). Oxiridagi ikki qatorga qarang — topilmadi va xatoIshlovchi:
// ilova.ts — Express ilovasi: middleware tartibi → routerlar → 404 →
// xato ishlovchisi.
// listen() YO'Q — server ishga-tushir.ts da, testlar ilovani portsiz
// ham ishlata oladi
import express from "express";
import cors from "cors";
import helmet from "helmet";
import { join } from "node:path";
import { sorovId } from "./umumiy/sorov-id.ts";
import {
idempotentlik,
XotiraIdempotentlikOmbori,
} from "./umumiy/idempotentlik.ts";
import type { IdempotentlikOmbori } from "./umumiy/idempotentlik.ts";
import { JSON_TURLARI } from "./umumiy/json-kerak.ts";
import { eskirgan, ESKI_YOLLAR } from "./umumiy/eskirgan.ts";
import {
topilmadi,
xatoIshlovchi,
} from "./umumiy/xato-ishlovchi.ts";
import type { XatoLogi } from "./umumiy/xato-ishlovchi.ts";
import { vazifaXizmati } from "./vazifalar/xizmat.ts";
import {
teglarRouteri,
vazifalarRouteri,
} from "./vazifalar/marshrut.ts";
import { eksportRouteri } from "./eksport/eksport.ts";
import type { VazifaOmbori } from "./vazifalar/ombor.ts";
export type Bogliqliklar = {
ombor: VazifaOmbori;
/**
* CORS ruxsat etilgan origin'lar (frontend: Vite dev, Pages) —
* bo'sh massiv: CORS sarlavhasi yo'q
*/
corsOriginlar?: readonly string[];
/** Idempotency-Key javoblari (27-qismda — Redis) */
idempotentlikOmbori?: IdempotentlikOmbori;
/**
* kutilmagan xatolarni yozish (sukut — console.error; #42 da Pino)
*/
xatoLogi?: XatoLogi;
};
export function ilovaYarat({
ombor,
corsOriginlar = [],
idempotentlikOmbori = new XotiraIdempotentlikOmbori(),
xatoLogi,
}: Bogliqliklar): express.Express {
const app = express();
app.disable("x-powered-by"); // "Express" ekanini oshkor qilmaymiz
app.use(sorovId());
// helmet: nosniff, HSTS, CSP va h.k. — JSON API uchun ham arzon
// (t14: 1 paket, import 2 ms)
app.use(helmet());
// CORS: frontend boshqa origin'dan (GitHub Pages) — faqat
// ro'yxatdagilar; preflight (OPTIONS) shu yerda javob oladi
app.use(
cors({
origin: [...corsOriginlar],
allowedHeaders: [
"Content-Type",
"Idempotency-Key",
"X-Request-Id",
"If-Match",
"If-None-Match",
],
exposedHeaders: [
"Location",
"X-Request-Id",
"Idempotency-Replayed",
"ETag",
"Link",
"Deprecation",
"Sunset",
],
maxAge: 600,
}),
);
app.use(
express.static(join(import.meta.dirname, "..", "public")),
);
app.use(express.json({ limit: "16kb", type: JSON_TURLARI }));
const xizmat = vazifaXizmati(ombor);
// Versiya URI'da: /v1/... Buzuvchi o'zgarish (maydon nomi, javob
// shakli) bo'lsa — /v2, v1 bir muddat yashaydi
const v1 = express.Router();
v1.use(
"/vazifalar",
idempotentlik(idempotentlikOmbori),
vazifalarRouteri(xizmat),
);
v1.use("/teglar", teglarRouteri(xizmat));
v1.use("/eksportlar", eksportRouteri(ombor));
app.use("/v1", v1);
// Eski versiyasiz yo'llar (holat 05–24) — ishlaydi, lekin eskirgan:
// Deprecation + Sunset + vorisga havola
app.use(eskirgan(ESKI_YOLLAR), v1);
app.use(topilmadi);
app.use(xatoIshlovchi(xatoLogi));
return app;
}test/xato.test.ts — to'liq (kanon holat 34):
import { after, before, test } from "node:test";
import assert from "node:assert/strict";
import { ishgaTushir as ishga } from "../src/ishga-tushir.ts";
import { yop } from "../src/yopish.ts";
import { XotiraOmbori } from "../src/vazifalar/xotira-ombor.ts";
import { jsonYubor } from "./yordamchi.ts";
import type { Problem } from "../src/umumiy/problem.ts";
// Fake ombor: haqiqiy xotira ombori, lekin ol() kutilmagan xato
// tashlaydi (DB uzildi kabi)
class BuzuqOmbor extends XotiraOmbori {
override ol(): Promise<undefined> {
return Promise.reject(
new Error("ulanish uzildi: parol=maxfiy"),
);
}
}
let asos = "";
let toxta: () => Promise<boolean>;
const loglar: unknown[][] = [];
const aslError = console.error;
before(async () => {
console.error = (...x: unknown[]) => loglar.push(x);
const { server, manzil } = await ishga({
port: 0,
bogliqliklar: { ombor: new BuzuqOmbor() },
});
asos = manzil;
toxta = () => yop(server, 1000);
});
after(async () => {
console.error = aslError;
await toxta();
});
test("Problem Details: domen xatosi (422) — type, title, detail, kod, xatolar, instance", async () => {
const r = await fetch(
`${asos}/v1/vazifalar`,
jsonYubor("POST", { matn: " " }),
);
assert.equal(r.status, 422);
assert.equal(
r.headers.get("content-type"),
"application/problem+json; charset=utf-8",
);
const p = (await r.json()) as Problem;
assert.deepEqual(p, {
type: "https://vazifalar-api.example/xatolar/validatsiya",
title: "So'rov ma'lumoti noto'g'ri",
status: 422,
detail: "matn 1–200 belgi bo'lishi kerak",
instance: "/v1/vazifalar",
kod: "validatsiya",
xatolar: [
{
joy: "body.matn",
xabar: "matn 1–200 belgi bo'lishi kerak",
},
],
sorovId: r.headers.get("x-request-id"),
});
});
test("Problem Details: 404 marshrut (about:blank), 405 Allow bilan, 415", async () => {
const yoq = (await (
await fetch(`${asos}/yoq?x=1`)
).json()) as Problem;
assert.equal(yoq.type, "about:blank");
assert.equal(yoq.title, "Not Found");
assert.equal(yoq.instance, "/yoq?x=1");
const r = await fetch(`${asos}/v1/teglar`, { method: "DELETE" });
assert.equal(r.status, 405);
assert.equal(r.headers.get("allow"), "GET, HEAD");
assert.equal(
((await r.json()) as Problem).title,
"Method Not Allowed",
);
const t = await fetch(`${asos}/v1/vazifalar`, {
method: "POST",
headers: { "Content-Type": "text/plain" },
body: "x",
});
assert.equal(((await t.json()) as Problem).status, 415);
});
test("500: ichki xabar mijozga chiqmaydi, logda sorovId bilan bor", async () => {
const r = await fetch(`${asos}/v1/vazifalar/1`, {
headers: { "X-Request-Id": "test-500-abcdef" },
});
assert.equal(r.status, 500);
const matn = await r.text();
assert.doesNotMatch(
matn,
/parol|maxfiy|ulanish uzildi|BuzuqOmbor|\.ts:\d+/,
);
assert.equal(
(JSON.parse(matn) as Problem).sorovId,
"test-500-abcdef",
);
assert.ok(
loglar.some(
(q) => q[0] === "[test-500-abcdef]" && q[1] instanceof Error,
),
);
});Tekshiring: npm run check.
Kesim tekshiruvi: tip 0, lint 0, testlar 35/35 — biz ishga tushirdik (Node 24.21.0). Kesim holat 34 nusxasidan yasaldi: keyingi darsning fayllari (jarayon.ts, test/jarayon.test.ts, qolip) olib tashlandi, server.ts holat 27 dagidek qoldi. Yakuniy holat 34 da (keyingi dars bilan) — 36/36.
10. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Ishlovchi hech qachon chaqirilmaydi | 3 ta argument | (err, req, res, next) — next qolsin |
| Javob HTML, stack bilan | Ishlovchi marshrutlardan oldin yoki umuman yo'q | Eng oxirida, topilmadi dan keyin |
| Hamma so'rov 404 | topilmadi routerlardan oldin |
Routerlardan keyin |
| Yaroqsiz JSON → 500 | Ishlovchi faqat o'z klasslarini taniydi | httpErrorsmi tarmog'i |
ERR_HTTP_HEADERS_SENT |
Javob yuborilgandan keyin res.json |
headersSent → next(xato) |
Mijoz detail da SQL ko'rdi |
detail: xato.message hamma xatoga |
Faqat IlovaXatosi uchun |
setTimeout dagi xato jarayonni o'ldirdi |
Express'dan tashqarida | Promise + await yoki next(xato) |
11. Mashqlar
Yechimlar kurs/mashqlar/25/33-xato-ishlovchi/ da.
1-mashq (oson): qaysi biri xato ishlovchi?
length qiymatini va Express uni xato ishlovchi deb biladimi — yozing.
(err, req, res, next) => {}— [:4], [:ha]function (e, q, s) {}— [:3], [:yo'q](err, req, res, _next) => {}— [:4], [:ha](err, req, res, next = () => {}) => {}— [:3], [:yo'q]
Yechim
- 4, ha — klassik shakl.
- 3, yo'q — nom ahamiyatsiz, soni 3.
- 4, ha —
_nextham parametr; pastki chiziq faqat linter uchun ishora. - 3, yo'q — sukut qiymatli parametr va undan keyingilar
lengthga kirmaydi. Bu kam uchraydigan, lekin haqiqiy tuzoq:nextga sukut qiymat bersangiz, ishlovchi jim o'chadi.
2-mashq (o'rta): modelga 404 qo'shing
Darsdagi zanjir modelini oling. Zanjir oxirigacha yetib, hech kim javob bermagan bo'lsa (javob.holat hali 200, tana bo'sh), 404 xatosi bilan xato ishlovchilarga qaytadan o'tishni emas, oddiyroq yo'lni tanlang: ro'yxatga topilmadi qatlamini qo'shing. Ikkita so'rovni sinang: { yol: "/salom" } (marshrut javob beradi) va { yol: "/yoq" }.
Yechim
function zanjir(qatlamlar) {
return (sorov) => {
const javob = { holat: 200, tana: "" };
let i = 0;
let xato;
function next(x) {
if (x !== undefined) xato = x;
const q = qatlamlar[i++];
if (!q) return;
const xatoUchun = q.length === 4;
if (Boolean(xato) !== xatoUchun) return next();
try {
if (xato) q(xato, sorov, javob, next);
else q(sorov, javob, next);
} catch (e) {
next(e);
}
}
next();
return javob;
};
}
class TopilmadiXatosi extends Error {
holat = 404;
}
const ilova = zanjir([
(req, res, next) => {
if (req.yol !== "/salom") return next();
res.tana = "salom";
},
(req) => {
throw new TopilmadiXatosi(`${req.yol} — bunday manzil yo'q`);
},
(err, req, res, next) => {
res.holat = err.holat ?? 500;
res.tana = err.holat ? err.message : "Kutilmagan xato";
},
]);
console.log(ilova({ yol: "/salom" }));
console.log(ilova({ yol: "/yoq" }));Konsolda:
{ holat: 200, tana: 'salom' }
{ holat: 404, tana: "/yoq — bunday manzil yo'q" }topilmadi qatlami shart tekshirmaydi — unga yetib kelgan har so'rov "topilmagan". Marshrut javob bersa, next() chaqirmaydi va zanjir to'xtaydi. Node satrda ' bo'lgani uchun uni qo'shtirnoq bilan ko'rsatdi.
3-mashq (qiyin): problemga(xato, sorovId)
Express'siz, toza funksiya yozing: u har qanday unknown qiymatni olib, { status, title, detail, sorovId } qaytaradi. Qoidalar kanondagidek. Birinchi: IlovaXatosi — o'z holati va xabari. Ikkinchi: status (son, < 500) va expose: true bor Error — o'sha status va type bo'yicha o'zbekcha matn (entity.parse.failed → "Tana yaroqli JSON emas"), bo'lmasa o'z xabari. Uchinchi: qolgani — 500, umumiy matn. title — kichik jadvaldan (400, 404, 413, 500).
Yechim
const NOMLAR = {
400: "Bad Request", 404: "Not Found",
413: "Payload Too Large", 500: "Internal Server Error",
};
const TANA = { "entity.parse.failed": "Tana yaroqli JSON emas" };
class IlovaXatosi extends Error {
constructor(holat, xabar) {
super(xabar);
this.holat = holat;
}
}
function problemga(xato, sorovId) {
let status = 500;
let detail = "Kutilmagan xato. sorovId bilan murojaat qiling.";
if (xato instanceof IlovaXatosi) {
status = xato.holat;
detail = xato.message;
} else if (xato instanceof Error && typeof xato.status === "number"
&& xato.status < 500 && xato.expose === true) {
status = xato.status;
detail = TANA[xato.type] ?? xato.message;
}
return { status, title: NOMLAR[status] ?? "Xato", detail, sorovId };
}
const parse = Object.assign(new SyntaxError("Unexpected end"),
{ status: 400, expose: true, type: "entity.parse.failed" });
const ichki = Object.assign(new Error("pool tugadi"),
{ status: 503, expose: false });
for (const x of [new IlovaXatosi(404, "Vazifa 7 topilmadi"),
parse, ichki, "satr"]) {
console.log(problemga(x, "abc12345").status,
problemga(x, "abc12345").detail);
}Konsolda:
404 Vazifa 7 topilmadi
400 Tana yaroqli JSON emas
500 Kutilmagan xato. sorovId bilan murojaat qiling.
500 Kutilmagan xato. sorovId bilan murojaat qiling.Uchinchisi — begona kutubxonaning 503 xatosi: status < 500 sharti uni yashirdi. To'rtinchisi — Error emas, satr: hech bir tarmoqqa tushmadi.
4-mashq: API qadami
Ishlovchini src/umumiy/xato-ishlovchi.ts ga chiqaring (topilmadi, xatoIshlovchi(logla)), ilova.ts ga xatoLogi bog'liqligini qo'shing. test/xato.test.ts da BuzuqOmbor (extends XotiraOmbori, ol() rad etadi) bilan 500 testini yozing: javobda parol yo'q, logda sorovId bor. npm run check — yashil (35/35). Commit qilmang.
Yechim
Kod — «Kanon» bo'limidagi to'liq xato-ishlovchi.ts va «API qadami» dagi ilova.ts va test/xato.test.ts. Testda console.error ni vaqtincha almashtirib, chaqiruvlarni massivga yig'ing (before/after da asliga qaytaring) yoki ishgaTushir ga xatoLogi bering — ikkinchisi tozaroq. Bu darsdan keyin 35/35; kanon holat 34 da testlar 36/36 (keyingi dars bilan).
12. Real ishda
- Har Express loyihasining oxirida shu ikki qator bor: 404 va xato ishlovchi. Begona repoda birinchi qaraladigan joy.
- NestJS'da xuddi shu g'oya — exception filter'lar; Next.js Route Handler'larida — o'zingiz yozadigan
try/catcho'rami. - Xato ishlovchi monitoring bilan bog'lanadi: Sentry SDK'si (34-qism) aynan shu joyga o'z ishlovchisini qo'yadi.
- Intervyu savollari: "Express xato middleware'ni qanday taniydi?", "Nega production'da stack ko'rsatilmaydi?", "Javob yarmida xato bo'lsa nima qilasiz?"
Xulosa
- Xato ishlovchi — 4 argumentli middleware; Express uni
fn.lengthbilan taniydi, nom bilan emas. - Tartib: routerlar →
topilmadi(404) →xatoIshlovchi— eng oxirida. - Uch tarmoq:
IlovaXatosi→ o'z statusi;http-errors(status < 500,expose) → o'zbekcha matn; qolgani → 500, tafsilot faqat logga. sorovIdmijoz javobini server logi bilan bog'laydi; javobda stack, fayl yo'li, ichki xabar — hech qachon.headersSentbo'lsa — log vanext(xato): ulanish uziladi.- Express'ning sukut ishlovchisi development'da stack'ni mijozga ko'rsatadi — o'z ishlovchingiz majburiy.
Keyingi dars: Process darajasidagi xatolar va crash strategiyasi — Express ushlay olmagan xatolar (unhandledRejection, uncaughtException) va nega "let it crash" eng xavfsiz yo'l.
Manbalar
- Express 5 hujjati — Error Handling (4 argument, sukut ishlovchi,
headersSent): https://expressjs.com/en/guide/error-handling.html - MDN —
Function.prototype.length(sukut qiymatli parametrlar): https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/length http-errors(status,expose) vabody-parserxato turlari: https://github.com/expressjs/body-parser#errors- RFC 9457 — Problem Details for HTTP APIs: https://www.rfc-editor.org/rfc/rfc9457
- Kanon:
vazifalar-apiholat 34 (src/umumiy/xato-ishlovchi.ts,test/xato.test.ts),natijalar/tajriba/t10-req-res.txt,t34-halokat.txt. Express 5.3.0 tajribalari (3 argument, sukut ishlovchi development/production, tartib, ishlovchi ichida xato,headersSent,next('route'), satr tashlash) va holat 34 jonli so'rovlari (400, 413, 404, 500) — biz ishga tushirdik (Node 24.21.0, Windows 11).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!