Mundarija (26)
- Bu darsda
- 1. Nega bu kerak?
- 2. Interceptor'ning tuzilishi
- 2.1 NestInterceptor va CallHandler
- 2.2 Lifecycle'dagi o'rni
- 3. RxJS — kerakli qismi
- 3.1 Observable nima
- 3.2 Model: pipe, map, tap
- 3.3 Interceptor uchun oltita nom
- 4. To'rt interceptor — haqiqiy chiqish
- 4.1 Timeout tuzog'i
- 5. Sarlavhalar: @Header yoki interceptor
- 6. Hujumchi nigohi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): operatorni tanlang
- 2-mashq (o'rta): interceptor zanjiri modeli
- 3-mashq (qiyin): timeout va bekor qilish
- 9. Loyiha qadami: ETag interceptor'da, Cache-Control — @Header
- 9.1 src/umumiy/vazifa-sarlavhalari.interceptor.ts
- 9.2 shartli.ts va teglar.controller.ts
- 9.3 vazifalar.controller.ts
- 9.4 Tekshiruv
- 10. Real ishda
- Xulosa
- Manbalar
Interceptor'lar va RxJS NestJS'da: map, tap, timeout va handler'ni o'rash
Qisqacha: Interceptor (ushlab qoluvchi) — handler'ni ikki tomondan o'raydigan klass:
intercept(kontekst, keyingi)handler'dan oldin ishlaydi,keyingi.handle()esa handler natijasini RxJS Observable (oqim) sifatida beradi. Unga operatorlar ulanadi:map— javobni o'zgartiradi (konvert{ ok, data }),tap— natijaga tegmasdan biror ish qiladi (log, sarlavha),catchError— xatoni boshqasiga aylantiradi,timeout— vaqt chegarasi.keyingi.handle()ni chaqirmasa — handler umuman ishlamaydi (kesh). Muhim tuzoq:timeoutjavobni to'xtatadi, lekin handler'ni to'xtatmaydi.
Bu darsda
- Interceptor handler'dan oldin va keyin qanday ishlashini tushunasiz.
- RxJS'ning eng kerakli qismini —
Observable,pipe,map,tap,catchError,timeout,of— o'rganasiz. - Javob konverti, vaqt o'lchash, timeout va kesh interceptor'larini haqiqiy chiqishda ko'rasiz.
@Headerva interceptor'ning sarlavha qo'yish farqini bilasiz.vazifalar-apida ETag va Last-Modified sarlavhalarini interceptor'ga ko'chirasiz.
Oldin bilishingiz kerak: Guard'lar, Javobni serializatsiya qilish, HTTP caching va shartli so'rovlar, Promise asoslari.
Javobni serializatsiya qilish darsida bitta tayyor interceptor ishlatdik — ClassSerializerInterceptor. Endi o'zimiznikini yozamiz va uning ichidagi RxJS bilan tanishamiz.
1. Nega bu kerak?
vazifalar-api ning bitta vazifa qaytaradigan har handler'i oxirida bir xil ish qiladi:
const v = await this.xizmat.yangila(id, body, ifMatch(req));
vazifaSarlavhalari(res, v); // ETag + Last-Modified
return v;Beshta handler'da vazifaSarlavhalari(res, v) — va shuning uchun beshtasida @Res({ passthrough: true }). Mantiq "javob tayyor bo'lgach, unga sarlavha qo'sh" — handler'ning ishi emas. Bu — handler'dan keyin bajariladigan umumiy ish.
Middleware handler'dan oldin ishlaydi va javob qiymatini ko'rmaydi. Guard ham — faqat oldin. Filtr — faqat xatoda. Handler'dan keyin, uning qaytargan qiymati bilan ishlaydigan qatlam — interceptor.
Request lifecycle darsidagi besh qatlamni shu savol bilan yana bir bor solishtiring — "handler qaytargan qiymatni kim ko'radi?":
| Qatlam | Qachon ishlaydi | Handler qaytargan qiymatni ko'radimi? |
|---|---|---|
| middleware | eng boshida, Nest'dan tashqarida | yo'q |
| guard (himoyachi) | handler'dan oldin, "o'tsinmi?" | yo'q |
| interceptor (ushlab qoluvchi) — kirish | guard'dan keyin, pipe'dan oldin | yo'q (hali yo'q) |
| pipe | handler'dan oldin, argumentlar uchun | yo'q |
| interceptor — chiqish | handler'dan keyin | ha — yagona qatlam |
| exception filter (xato filtri) | faqat xato bo'lsa | yo'q — faqat xatoni |
Jadvalning beshinchi qatori — bu darsning mavzusi. Interceptor ikki marta uchraydi: bir xil klass handler'dan oldin ham, keyin ham ishlaydi.
Interceptor'ning odatiy ishlari:
- javobni o'zgartirish yoki o'rash (
{ ok: true, data }); - vaqtni o'lchash va log yozish;
- vaqt chegarasi (timeout);
- kesh: javob tayyor bo'lsa — handler'ni chaqirmaslik;
- xatoni boshqa xatoga aylantirish.
O'xshatish: sovg'a qadoqlovchi. Do'kondor (handler) mahsulotni beradi. Qadoqlovchi undan oldin "qaysi qog'oz" ni tanlaydi, keyin mahsulotni o'raydi, lenta bog'laydi. Ba'zan esa javon'da tayyor qadoqlangan nusxa bor — do'kondorni bezovta qilmay, darhol beradi.
2. Interceptor'ning tuzilishi
2.1 NestInterceptor va CallHandler
@Injectable()
class Vaqt implements NestInterceptor {
intercept(
c: ExecutionContext,
n: CallHandler,
): Observable<unknown> {
const t = performance.now(); // 1) handler'dan OLDIN
return n.handle().pipe( // 2) handler shu yerda
tap(() => { // 3) handler'dan KEYIN
const ms = (performance.now() - t).toFixed(0);
console.log(` [Vaqt] ${c.getClass().name}.` +
`${c.getHandler().name}: ${ms} ms`);
}),
);
}
}intercept(kontekst, keyingi)— guard'dagi o'shaExecutionContextvaCallHandler.keyingi.handle()— handler'ni chaqiradi (aniqrog'i, chaqirishga tayyor oqim qaytaradi). Uni chaqirmasangiz — handler ishlamaydi..pipe(...)— oqimga operatorlar ulanadi. Ular handler natijasi kelganda ishlaydi.
intercept dan oldingi qism — handler'dan oldin, pipe ichidagi qism — handler'dan keyin. Bitta funksiyada ikkala tomon.
2.2 Lifecycle'dagi o'rni
Kanon tajribasida interceptor'lar ikki marta ko'rinadi — "oldin" va "keyin" (kurs tajribasi, NestJS 12.2.0, Vitest ichida). Raqamlarga emas, qavs ichidagi so'zga va daraja nomlarining tartibiga qarang:
7. interceptor (oldin): global (APP_INTERCEPTOR)
8. interceptor (oldin): global (useGlobalInterceptors)
9. interceptor (oldin): controller
10. interceptor (oldin): route
11. pipe: global (APP_PIPE) (param:id)
…
16. HANDLER
17. interceptor (keyin): route
18. interceptor (keyin): controller
19. interceptor (keyin): global (useGlobalInterceptors)
20. interceptor (keyin): global (APP_INTERCEPTOR)
21. javob ketdi: 200"Oldin" tartibi — global → route, "keyin" — teskari: route → global. Bu piyoz qatlamlari: tashqi qatlam birinchi kiradi va oxirgi chiqadi.
flowchart LR
G1["global (oldin)"] --> R1["route (oldin)"]
R1 --> P["pipe'lar"]
P --> H["HANDLER"]
H --> R2["route (keyin)"]
R2 --> G2["global (keyin)"]
G2 --> J["javob"]E'tibor bering: interceptor pipe'lardan oldin kiradi. Ya'ni "oldin" qismi ishlayotganda parametrlar hali tekshirilmagan. Pipe rad etsa — handler chaqirilmaydi, lekin interceptor'larning "xato" yo'li ishlaydi (tajribaning ikkinchi holati — "interceptor (xato): route").
3. RxJS — kerakli qismi
3.1 Observable nima
Observable (kuzatiladigan oqim) — vaqt o'tishi bilan qiymat(lar) beradigan manba. Promise bitta qiymat beradi va tugaydi; Observable bir nechta berishi mumkin. Nest HTTP handler uchun bitta qiymat beradi — shu ma'noda u Promise'ga juda o'xshaydi. Farq — operatorlar: pipe(map(...), tap(...)) bilan qiymat kelishidan oldin "nima qilish kerak" yozib qo'yiladi.
RxJS — Observable kutubxonasi. Nest uni ichida ishlatadi (rxjs 7.8.2 — vazifalar-api ning prod bog'liqligi, nest new uni o'zi qo'shadi). Kursda RxJS'ni chuqur o'tmaymiz — interceptor uchun kerak bo'ladigan oltita nom yetadi.
3.2 Model: pipe, map, tap
RxJS saytdagi runner'da yo'q. G'oyani ko'rish uchun o'z kichik modelimizni yozamiz: "oqim" — natijani Promise sifatida beradigan obyekt, operator — oqimdan yangi oqim yasaydigan funksiya.
const oqim = (fn) => ({
qiymat: fn,
pipe(...operatorlar) {
return operatorlar.reduce((o, op) => op(o), this);
},
});
const map = (f) => (o) => oqim(async () => f(await o.qiymat()));
const tap = (f) => (o) => oqim(async () => {
const v = await o.qiymat();
f(v);
return v;
});
const handler = oqim(async () => [1, 2]);
const javob = handler.pipe(
tap((v) => console.log("handler qaytardi:", v)),
map((data) => ({ ok: true, data })),
);
console.log("pipe yozildi, handler hali ishlamadi");
console.log(JSON.stringify(await javob.qiymat()));Konsolda:
pipe yozildi, handler hali ishlamadi
handler qaytardi: [ 1, 2 ]
{"ok":true,"data":[1,2]}Uch narsa modelda ham, RxJS'da ham bir xil:
pipe(...)faqat reja tuzadi; handlerqiymat()(RxJS'da — obuna,subscribe) chaqirilgandagina ishlaydi. Birinchi qatorga qarang.tapqiymatni ko'radi, lekin o'zgartirmaydi.mapqiymatni almashtiradi.
Model soddalashtirilgan: RxJS Observable bir nechta qiymat, bekor qilish (unsubscribe) va xato kanalini ham qo'llaydi.
3.3 Interceptor uchun oltita nom
| Nom | Nima qiladi | Interceptor'da |
|---|---|---|
map(f) |
qiymatni f(qiymat) ga almashtiradi |
javob konverti |
tap(f) |
qiymatni o'zgartirmay f ni chaqiradi |
log, vaqt, sarlavha |
catchError(f) |
xatoni ushlaydi, yangi oqim qaytaradi | xatoni aylantirish |
timeout(ms) |
ms ichida qiymat kelmasa — TimeoutError |
vaqt chegarasi |
of(x) |
darhol x beradigan oqim |
kesh: handler'siz javob |
throwError(() => x) |
darhol xato beradigan oqim | catchError ichida |
4. To'rt interceptor — haqiqiy chiqish
Kanon tajribasida to'rtta interceptor yozilgan va controller'ga ulangan (kurs tajribasi, NestJS 12.2.0, Vitest ichida). Kod (telefon uchun bo'lingan, ma'no aynan):
@Injectable() class Konvert implements NestInterceptor {
intercept(_c: ExecutionContext, n: CallHandler) {
return n.handle().pipe(
map((data: unknown) => ({ ok: true, data })),
);
}
}
@Injectable() class Chegara implements NestInterceptor {
intercept(_c: ExecutionContext, n: CallHandler) {
return n.handle().pipe(
timeout(100),
catchError((x: unknown) => throwError(() =>
x instanceof TimeoutError
? new ServiceUnavailableException(
"100 ms ichida javob yo'q")
: x)),
);
}
}
let chaqiruv = 0;
@Injectable() class Kesh implements NestInterceptor {
intercept(_c: ExecutionContext, n: CallHandler) {
return chaqiruv > 0 ? of({ keshdan: true }) : n.handle();
}
}
@Controller()
@UseInterceptors(Vaqt)
class C {
@Get("konvert") @UseInterceptors(Konvert)
konvert() { return [1, 2]; }
@Get("tez") @UseInterceptors(Chegara) async tez() {
await new Promise((r) => setTimeout(r, 20)); return "tez";
}
@Get("sekin") @UseInterceptors(Chegara) async sekin() {
await new Promise((r) => setTimeout(r, 300));
console.log(" [handler] sekin() baribir tugadi " +
"(timeout handler'ni TO'XTATMAYDI)");
return "sekin";
}
@Get("kesh") @UseInterceptors(Kesh) kesh() {
chaqiruv++; return { handler: chaqiruv };
}
}Vaqt — controller darajasida (hamma marshrutga), qolganlari — metodlarda. Natijada uch narsaga qarang: har so'rovning status kodi, [Vaqt] qatori bor yoki yo'qligi va eng oxirgi qator:
[Vaqt] C.konvert: 1 ms
GET /konvert → 200 {"ok":true,"data":[1,2]}
[Vaqt] C.tez: 31 ms
GET /tez → 200 tez
GET /sekin → 503 {"message":"100 ms ichida javob yo'q","error":"Service Unavailable","statusCode":503}
[Vaqt] C.kesh: 0 ms
GET /kesh → 200 {"handler":1}
[Vaqt] C.kesh: 0 ms
GET /kesh → 200 {"keshdan":true}
[handler] sekin() baribir tugadi (timeout handler'ni TO'XTATMAYDI)(Millisekundlar har yurgizishda boshqacha.) Har qatorni o'qing:
- Konvert — handler
[1, 2]qaytardi, mijoz{"ok":true,"data":[1,2]}oldi.mapishladi. - Vaqt —
taphandler'dan keyin vaqtni yozdi./sekinuchun[Vaqt]qatori yo'q: oqim xato bilan tugadi,tapning birinchi argumenti faqat muvaffaqiyatli qiymatda chaqiriladi. - Chegara —
/tez(20 ms) o'tdi,/sekin(300 ms) 100 ms da 503 oldi:timeout→TimeoutError→catchErroruniServiceUnavailableExceptionga aylantirdi. - Kesh — birinchi
/keshhandler'ga bordi ({"handler":1}), ikkinchisiof(...)dan — handler chaqirilmadi. - Oxirgi qator — eng muhimi.
4.1 Timeout tuzog'i
Mijoz 503 ni 100 ms da oldi. Lekin oxirgi qatorga qarang: 300 ms dan keyin sekin() baribir tugadi va natija hech kimga kerak bo'lmay qoldi. timeout faqat oqimdan voz kechdi. Handler — oddiy async funksiya, uning Promise ini RxJS bekor qila olmaydi. JavaScript'da Promise'ni tashqaridan to'xtatish yo'q.
Bu nimani anglatadi:
- Handler bazaga yozayotgan bo'lsa — mijoz 503 oldi, lekin yozuv bo'ldi. Mijoz qayta urinadi — ikkinchi yozuv. (Idempotentlik kaliti shu holatdan himoya qiladi — Idempotency-Key.)
- Sekin so'rovlar ko'p bo'lsa — mijozlarga tez 503 ketadi, lekin server baribir ularning hammasini oxirigacha bajaradi. Yuk kamaymaydi.
Haqiqiy bekor qilish uchun ishni ichkaridan to'xtatish kerak: AbortSignal ni handler'gacha va undan bazagacha uzatish. 25-qismdagi server timeout'lari (Server timeout'lari) ham ulanishni yopadi, lekin handler'ni to'xtatmaydi.
Tekshirib ko'ring: Interceptor'da
timeout(5000)bor. To'lov handler'i tashqi API'ni chaqiradi va 8 soniyada javob oladi. Mijoz nima oladi va to'lov holati qanday?
Javob
Mijoz 5 soniyada xato oladi (timeout'ni nima ga aylantirgan bo'lsangiz — masalan 503). Handler esa davom etadi va 8-soniyada to'lov amalga oshadi. Mijoz "to'lov o'tmadi" deb o'ylaydi va qayta to'laydi — ikki marta pul yechiladi. Yechim: to'lov uchun idempotentlik kaliti, tashqi chaqiruvning o'z timeout'i (AbortSignal bilan) va holatni keyin tekshirish imkoniyati (29-qismdagi reconciliation).
5. Sarlavhalar: @Header yoki interceptor
Sarlavha qo'yishning ikki Nest usuli bor:
@Header("Cache-Control", "…") |
interceptor tap |
|
|---|---|---|
| Qiymat | o'zgarmas | javobga bog'liq (ETag — versiyadan) |
| Qachon qo'yiladi | handler'dan oldin | handler muvaffaqiyatli tugagach |
| Xato javobida | bor | yo'q |
Oxirgi qatorni kanon o'lchagan. GET /v1/vazifalar/28 — o'chirilgan vazifa, 404. Interceptor va @Header dan oldingi holat (kanon tekshiruvi, Postgres 18):
$ curl /v1/vazifalar/28
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8Keyingi holat — Cache-Control @Header ga ko'chgach:
$ curl /v1/vazifalar/28
HTTP/1.1 404 Not Found
Cache-Control: private, no-cache
Content-Type: application/problem+json; charset=utf-8(Ikkalasida Content-Length, ETag va tana qatorlarini tushirib qoldirdik.) @Header handler'dan oldin qo'yilgani uchun 404 javobida ham qoldi. Express versiyasida keshYoq(res) handler ichida, muvaffaqiyatdan keyin edi — xatoda yo'q edi. Bu farq zararsiz: xato javobini ham "har safar tekshir" deb belgilash to'g'ri. Kanon uni ataylab qoldirdi.
ETag uchun esa @Header yaramaydi — qiymat vazifa versiyasidan: "3". Bu — interceptor.
6. Hujumchi nigohi
- Sekin so'rov bilan yuk. Timeout interceptor'i mijozni tez qaytaradi, lekin handler davom etadi. Hujumchi og'ir so'rovlarni ketma-ket yuboradi: har biri 503 oladi, server esa hammasini bajaradi. Himoya — ishni ichkaridan bekor qilish (
AbortSignal), og'ir endpointlarga tezlik cheklovi. - Konvertda ma'lumot oqishi.
map((data) => ({ ok: true, data, debug: kontekst }))kabi "yordamchi" maydon — so'rov obyekti, sarlavhalar, ichki ID'lar javobga tushadi. Himoya — konvertga faqat aniq maydonlar. - Kesh interceptor'ining kaliti. Kesh kaliti foydalanuvchini hisobga olmasa (
/men— hamma uchun bitta kalit), birinchi foydalanuvchining javobi keyingilarga ketadi. Himoya — kalitga foydalanuvchi ID'si va so'rov parametrlari; shaxsiy javoblarni umumiy keshga qo'ymaslik. catchErrorbilan xatoni yutish.catchError(() => of(null))— har xato 200 vanullbo'ladi; xatolar logga ham, monitoringga ham yetmaydi. Himoya —catchErrorfaqat aniq xato turini aylantirsin, qolganinithrowErrorbilan qayta tashlasin (kanonChegaradagidek).
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
| Handler umuman ishlamaydi | keyingi.handle() chaqirilmagan |
return keyingi.handle().pipe(...) |
map dagi o'zgarish javobda yo'q |
@Res() passthrough'siz — javobni o'zingiz yubordingiz |
return qiymat yoki passthrough |
| Timeout 503 berdi, lekin yozuv bo'ldi | timeout handler'ni to'xtatmaydi |
AbortSignal, idempotentlik |
tap da xato javob uchun log yo'q |
tap(f) faqat muvaffaqiyatda |
tap({ next, error }) yoki catchError |
| Sarlavha 404 da ham bor | @Header handler'dan oldin |
kerak bo'lsa interceptor tap |
| Hamma xato 200 bo'ldi | catchError(() => of(...)) |
aniq turni ushlab, qolganini qayta tashlang |
8. Mashqlar
Mashqlar kurs/mashqlar/30/23-interceptorlar/ da.
1-mashq (oson): operatorni tanlang
- Javobni
{ ok: true, data }ga o'rash — - Javobga tegmasdan vaqtni logga yozish —
- 100 ms ichida javob bo'lmasa xato —
(100) - Handler'siz, darhol javob —
({ keshdan: true }) - Handler'ni chaqiradigan metod —
keyingi.()
Yechim
- map — qiymatni almashtiradi.
- tap — qiymatni o'zgartirmaydi; sarlavha va log uchun.
- timeout —
TimeoutError; handler'ni to'xtatmaydi. - of — tayyor qiymatli oqim.
- handle — chaqirilmasa, handler ishlamaydi.
2-mashq (o'rta): interceptor zanjiri modeli
Interceptor — (kontekst, keyingi) => Promise funksiya, keyingi() — keyingi qatlamni (oxirida handler'ni) chaqiradi. zanjir(interceptorlar, handler) funksiyasini yozing: birinchi interceptor eng tashqi bo'lsin. Har interceptor "oldin" va "keyin" ni logga yozsin. Ikki interceptor (global, route) bilan "piyoz" tartibini chiqaring. Ishora: reduceRight (reduce).
Yechim
function zanjir(interceptorlar, handler) {
return interceptorlar.reduceRight(
(keyingi, ic) => () => ic(keyingi),
handler,
);
}
const yozuvchi = (nom) => async (keyingi) => {
console.log(`${nom}: oldin`);
const v = await keyingi();
console.log(`${nom}: keyin`);
return v;
};
const handler = async () => {
console.log("HANDLER");
return "javob";
};
const qatlamlar = [yozuvchi("global"), yozuvchi("route")];
const ishla = zanjir(qatlamlar, handler);
console.log(await ishla());Konsolda:
global: oldin
route: oldin
HANDLER
route: keyin
global: keyin
javobKanon lifecycle tajribasidagi tartib aynan shunday: "oldin" — global → route, "keyin" — route → global. reduceRight ro'yxatni oxiridan yig'adi: handler'ni avval route o'raydi, uni esa global.
3-mashq (qiyin): timeout va bekor qilish
chegara(ms, ish) funksiyasini ikki variantda yozing. (1) Promise.race bilan: ms dan keyin "503" qaytaradi, lekin ish davom etadi. (2) AbortController bilan: ms dan keyin signal yuboriladi va ish uni tekshirib to'xtaydi. ish(signal) — 3 qadamli, har qadam 40 ms, har qadamda "qadam N" yozadi va signal kelgan bo'lsa to'xtaydi. ms = 100. Oxirida hamma qadamlar tugashini 200 ms kuting va ikki variantning logini solishtiring. Ishora: AbortController, signal.aborted (AbortController), setTimeout.
Yechim
const kut = (ms) => new Promise((r) => setTimeout(r, ms));
async function ish(nom, signal) {
for (let i = 1; i <= 3; i++) {
await kut(40);
if (signal?.aborted) {
console.log(`${nom}: to'xtadi (qadam ${i})`);
return;
}
console.log(`${nom}: qadam ${i}`);
}
}
async function race(ms) {
const vaqt = kut(ms).then(() => "503");
return Promise.race([ish("race").then(() => "200"), vaqt]);
}
async function abort(ms) {
const ac = new AbortController();
setTimeout(() => ac.abort(), ms);
await ish("abort", ac.signal);
return ac.signal.aborted ? "503" : "200";
}
console.log("race →", await race(100));
await kut(200);
console.log("abort →", await abort(100));Konsolda:
race: qadam 1
race: qadam 2
race → 503
race: qadam 3
abort: qadam 1
abort: qadam 2
abort: to'xtadi (qadam 3)
abort → 503Ikkalasi ham mijozga 503 berdi. Lekin race da "qadam 3" javobdan keyin bajarildi — RxJS timeout ning xulqi aynan shu. abort da ish signalni ko'rib to'xtadi — uchinchi qadam bajarilmadi. Haqiqiy loyihada signal fetch, pg so'rovi yoki Prisma tranzaksiyasigacha uzatiladi.
9. Loyiha qadami: ETag interceptor'da, Cache-Control — @Header
9.1 src/umumiy/vazifa-sarlavhalari.interceptor.ts
Kanon faylida qatorlar uzun edi — bo'ldik va izohlarni qisqartirdik (kod ma'nosi aynan):
// vazifa-sarlavhalari.interceptor.ts — bitta vazifa javobiga ETag
// ("versiya") va Last-Modified — handler qaytargan qiymatdan
// (RxJS tap). Interceptor javob yuborilishidan OLDIN ishlaydi:
// Express res.json() ETag'ni ko'rib If-None-Match mos kelsa 304
// beradi (25 dagidek)
import { Injectable } from "@nestjs/common";
import type {
CallHandler, ExecutionContext, NestInterceptor,
} from "@nestjs/common";
import type { Response } from "express";
import { tap } from "rxjs";
import type { Observable } from "rxjs";
import { vazifaSarlavhalari } from "./shartli.ts";
import type { Vazifa } from "../vazifalar/vazifa.ts";
@Injectable()
export class VazifaSarlavhalariInterceptor
implements NestInterceptor<Vazifa, Vazifa> {
intercept(
kontekst: ExecutionContext,
keyingi: CallHandler<Vazifa>,
): Observable<Vazifa> {
const res = kontekst.switchToHttp().getResponse<Response>();
return keyingi.handle().pipe(
tap((v) => { vazifaSarlavhalari(res, v); }),
);
}
}tap— javob qiymati (Vazifa) o'zgarmaydi, faqat sarlavhalar qo'yiladi.- Nega 304 ishlaydi:
taphandler natijasi kelganda, lekin Nest javobnires.json()bilan yuborishdan oldin ishlaydi. Expressres.json()ichidaETagva so'rovningIf-None-Matchini solishtiradi va mos kelsa 304 beradi (HTTP caching). NestInterceptor<Vazifa, Vazifa>— kirish va chiqish turlari; interceptor faqat vazifa qaytaradigan handler'larga ulanadi.vazifaSarlavhalari()(shartli.ts) — 25-qism funksiyasi, o'zgarmaydi.
9.2 shartli.ts va teglar.controller.ts
shartli.ts dan keshYoq() olib tashlanadi — Cache-Control endi controller'larda @Header bilan:
-/** API javoblari: brauzer saqlashi mumkin, lekin har safar
- * tekshirsin (private — umumiy keshlar saqlamasin) */
-export function keshYoq(res: Response): void {
- res.set("Cache-Control", "private, no-cache");
-}teglar.controller.ts — @Res() ham kerak bo'lmay qoldi:
// teglar.controller.ts — /v1/teglar: teglar va nechta vazifada
import { All, Controller, Get, Header, Req } from "@nestjs/common";
import type { Request } from "express";
import { MetodYoqXatosi } from "../umumiy/xatolar.ts";
import type { TegSoni } from "./ombor.ts";
import { VazifalarService } from "./vazifalar.service.ts";
@Controller("teglar")
export class TeglarController {
constructor(private readonly xizmat: VazifalarService) {}
@Get()
@Header("Cache-Control", "private, no-cache")
async teglar(): Promise<{ teglar: TegSoni[] }> {
return { teglar: await this.xizmat.teglar() };
}
@All()
metodYoq(@Req() req: Request): never {
throw new MetodYoqXatosi(req.method, "GET, HEAD");
}
}9.3 vazifalar.controller.ts
Importlar va beshta handler o'zgaradi. Diff (faqat o'zgargan joylar; … — o'zgarishsiz qism):
import {
- All, Body, Controller, Delete, Get, HttpCode, Inject, Param, Patch,
- Post, Put, Query, Req, Res,
+ All, Body, Controller, Delete, Get, Header, HttpCode, Inject,
+ Param, Patch, Post, Put, Query, Req, Res, UseInterceptors,
} from "@nestjs/common";
…
-import {
- ifMatch, keshYoq, vazifaSarlavhalari,
-} from "../umumiy/shartli.ts";
+import { ifMatch } from "../umumiy/shartli.ts";
+import {
+ VazifaSarlavhalariInterceptor,
+} from "../umumiy/vazifa-sarlavhalari.interceptor.ts";
…
+/** API javoblari: brauzer saqlashi mumkin, lekin har safar
+ * tekshirsin (private — umumiy keshlar saqlamasin) */
+const KESH = ["Cache-Control", "private, no-cache"] as const;
…
@Get()
+ @Header(...KESH)
async royxat(…) {
…
- keshYoq(res);
return { … };
}
@Post()
@JsonTana()
+ @UseInterceptors(VazifaSarlavhalariInterceptor)
async yarat(…) {
const v = await this.xizmat.yarat(body);
- vazifaSarlavhalari(res, v);
res.location(…);
return v;
}
@Put()
@JsonTana()
+ @Header(...KESH)
async almashtirHammasi(
@Body({ schema: ToplamSxemasi })
body: { vazifalar: AlmashtirishVazifasi[] },
- @Res({ passthrough: true }) res: Response,
): Promise<object> {
…
- keshYoq(res);
return { versiya, vazifalar };
}
…
@Get(":id")
- async ol(
- @Param({ schema: IdSxemasi }) { id }: Id,
- @Res({ passthrough: true }) res: Response,
- ): Promise<Vazifa> {
- const v = await this.xizmat.ol(id);
- vazifaSarlavhalari(res, v);
- keshYoq(res);
- return v;
- }
+ @Header(...KESH)
+ @UseInterceptors(VazifaSarlavhalariInterceptor)
+ ol(@Param({ schema: IdSxemasi }) { id }: Id): Promise<Vazifa> {
+ return this.xizmat.ol(id);
+ }almashtir (PUT /:id) va yangila (PATCH) — yarat kabi: @UseInterceptors(VazifaSarlavhalariInterceptor) qo'shiladi, vazifaSarlavhalari(res, v) va @Res(...) parametri o'chadi, metod esa natijani to'g'ridan qaytaradi:
@Put(":id")
@JsonTana()
@UseInterceptors(VazifaSarlavhalariInterceptor)
almashtir(
@Param({ schema: IdSxemasi }) { id }: Id,
@Body({ schema: ToliqVazifaSxemasi }) body: ToliqVazifa,
@Req() req: Request,
): Promise<Vazifa> {
return this.xizmat.almashtir(id, body, ifMatch(req));
}@Header(...KESH)— spread (Spread va rest) bilan ikki argument: nom va qiymat.as const— massiv turireadonly ["Cache-Control", "private, no-cache"], aks holda TypeScriptstring[]ni ikki parametrga yoya olmaydi.olendiasyncemas —Promiseni o'zi qaytaradi. InterceptorObservablega Promise natijasini ham oladi.@Req()halialmashtir,yangila,ochirda —ifMatch(req)uchun;yaratvaroyxatda —asosYoluchun. Ularni Custom dekoratorlar darsida o'z parametr dekoratorlarimiz bilan almashtiramiz.
9.4 Tekshiruv
npm run tip, npm run lint, npm test — toza (Postgres'siz: Tests 75 passed | 6 skipped (81)). 25-qismdagi ETag va 304 testlari (shartli.test) o'zgarishsiz o'tadi — sarlavhalar javob yuborilishidan oldin qo'yilishining isboti. Hali commit qilmang.
10. Real ishda
- Logging interceptor — har so'rovning vaqti va statusi; ko'p jamoalarda birinchi yoziladigan interceptor. (
vazifalar-apida bu ishni pino-http middleware'i qiladi — u 404 va pipe xatolarini ham ko'radi.) - Javob konverti (
{ data, meta }) — ba'zi API standartlari talab qiladi; REST'da ko'pincha shart emas. ClassSerializerInterceptor,CacheInterceptor(CacheModule) — Nest'ning tayyor interceptor'lari.- Intervyu: "interceptor va middleware farqi?", "interceptor handler'ni chaqirmasligi mumkinmi?", "RxJS
timeouthandler'ni to'xtatadimi?".
Xulosa
- Interceptor —
intercept(kontekst, keyingi): oldin — koddahandle()gacha, keyin —handle().pipe(...)ichida; tartib — piyoz (oldin global → route, keyin teskari). - RxJS:
map(almashtirish),tap(kuzatish),catchError,timeout,of,throwError;pipe— reja, obuna — bajarish. keyingi.handle()chaqirilmasa — handler ishlamaydi (kesh naqshi).timeouthandler'ni to'xtatmaydi — bekor qilishAbortSignalbilan, idempotentlik bilan.@Header— o'zgarmas, handler'dan oldin (xatoda ham); interceptortap— javobga bog'liq sarlavhalar.
Keyingi dars: Exception filter'lar — HttpException oilasi, @Catch, ArgumentsHost, NestJS 12 ning errorCode i va vazifalar-api ning Problem Details filtri DI bilan.
Manbalar
- NestJS hujjati — Interceptors (
NestInterceptor,CallHandler, response mapping, timeout): https://docs.nestjs.com/interceptors - RxJS hujjati — operatorlar (
map,tap,catchError,timeout,of): https://rxjs.dev/guide/operators - MDN —
AbortController: https://developer.mozilla.org/en-US/docs/Web/API/AbortController - Kanon (IlmHamroh, 2026-10-27): interceptor tajribasi va lifecycle tartibi (NestJS 12.2.0, Vitest ichida),
vazifalar-apitekshiruvlari (404 dagiCache-Control— oldingi va keyingi holat, Postgres 18). Oraliq holat tekshiruvi — biz ishga tushirdik (2026-11-02, Node 24.21.0).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!