IlmHamroh
JavaScript Full-stack/30-qism. NestJS23/65-dars18 daqiqa
Mundarija (26)

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: timeout javobni 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.
  • @Header va interceptor'ning sarlavha qo'yish farqini bilasiz.
  • vazifalar-api da 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:

ts
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

ts
@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'sha ExecutionContext va CallHandler.
  • 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:

text
   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.

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

text
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; handler qiymat() (RxJS'da — obuna, subscribe) chaqirilgandagina ishlaydi. Birinchi qatorga qarang.
  • tap qiymatni ko'radi, lekin o'zgartirmaydi.
  • map qiymatni 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):

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

text
  [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. map ishladi.
  • Vaqt — tap handler'dan keyin vaqtni yozdi. /sekin uchun [Vaqt] qatori yo'q: oqim xato bilan tugadi, tap ning birinchi argumenti faqat muvaffaqiyatli qiymatda chaqiriladi.
  • Chegara — /tez (20 ms) o'tdi, /sekin (300 ms) 100 ms da 503 oldi: timeout → TimeoutError → catchError uni ServiceUnavailableException ga aylantirdi.
  • Kesh — birinchi /kesh handler'ga bordi ({"handler":1}), ikkinchisi of(...) 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):

text
$ curl /v1/vazifalar/28
HTTP/1.1 404 Not Found
Content-Type: application/problem+json; charset=utf-8

Keyingi holat — Cache-Control @Header ga ko'chgach:

text
$ 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

  1. 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.
  2. 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.
  3. 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.
  4. catchError bilan xatoni yutish. catchError(() => of(null)) — har xato 200 va null bo'ladi; xatolar logga ham, monitoringga ham yetmaydi. Himoya — catchError faqat aniq xato turini aylantirsin, qolganini throwError bilan qayta tashlasin (kanon Chegara dagidek).

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

text
global: oldin
route: oldin
HANDLER
route: keyin
global: keyin
javob

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

text
race: qadam 1
race: qadam 2
race → 503
race: qadam 3
abort: qadam 1
abort: qadam 2
abort: to'xtadi (qadam 3)
abort → 503

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

ts
// 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: tap handler natijasi kelganda, lekin Nest javobni res.json() bilan yuborishdan oldin ishlaydi. Express res.json() ichida ETag va so'rovning If-None-Match ini 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:

diff
-/** 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:

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

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

ts
@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 turi readonly ["Cache-Control", "private, no-cache"], aks holda TypeScript string[] ni ikki parametrga yoya olmaydi.
  • ol endi async emas — Promise ni o'zi qaytaradi. Interceptor Observable ga Promise natijasini ham oladi.
  • @Req() hali almashtir, yangila, ochir da — ifMatch(req) uchun; yarat va royxat da — asosYol uchun. 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-api da 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 timeout handler'ni to'xtatadimi?".

Xulosa

  • Interceptor — intercept(kontekst, keyingi): oldin — kodda handle() 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).
  • timeout handler'ni to'xtatmaydi — bekor qilish AbortSignal bilan, idempotentlik bilan.
  • @Header — o'zgarmas, handler'dan oldin (xatoda ham); interceptor tap — 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

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Interceptor'lar va RxJS NestJS'da: map, tap, timeout va handler'ni o'rash — IlmHamroh