Mundarija (30)
- Bu darsda
- 1. Nega bu kerak?
- 2. To'rt shakl
- 2.1 Umumiy ko'rinish
- 2.2 useValue
- 2.3 useClass
- 2.4 useFactory + inject
- 2.5 useExisting
- 3. Kanon tajribasi: hamma shakl bir joyda
- 4. JS modelida provider turlari
- 5. Async fabrika: ilova kutadi
- 5.1 Nima uchun async
- 5.2 Biz o'lchadik
- 6. Qaysi shaklni tanlash
- 6.1 Uch savol
- 6.2 Testda almashtirish
- 6.3 Provider'lar qayerda yashaydi
- 7. Ko'p uchraydigan xatolar
- 8. Mashqlar
- 1-mashq (oson): qaysi shakl?
- 2-mashq (o'rta): muhitga qarab provider
- 3-mashq (qiyin): ixtiyoriy inject
- 9. Loyiha qadami: ombor fabrikasi
- 9.1 Reja
- 9.2 ombor-yarat.ts
- 9.3 ombor.module.ts
- 9.4 Tekshiruv
- 10. Real ishda
- Xulosa
- Manbalar
NestJS custom provider'lar: useValue, useClass, useFactory, useExisting va async fabrika
Qisqacha: Provider'ni klass nomi bilan emas, obyekt bilan ham yozish mumkin:
{ provide: TOKEN, … }. To'rt shakli bor:useValue— tayyor qiymat;useClass— shu token uchun boshqa klass;useFactory— qiymatni funksiya qaytaradi, unga boshqa provider'larinjectorqali beriladi;useExisting— boshqa tokenga taxallus. FabrikaPromiseqaytarsa, Nest uni kutadi — ilova faqat u tayyor bo'lgach ishga tushadi. Bu baza ulanishi kabi asinxron resurslar uchun asosiy usul.
Bu darsda
- Provider'ning to'rt shaklini va har biri qaysi muammoni hal qilishini bilasiz.
useFactorygainjectbilan boshqa provider'larni berasiz.- Async fabrika ilova ishga tushishini qanday "ushlab turishini" haqiqiy o'lchov bilan ko'rasiz.
- Provider turlarini 40 qatorlik JS konteynerida o'zingiz ishlatib ko'rasiz.
vazifalar-apiomborini Nest ichida, config bo'yicha yaratadigan provider'larni yozasiz.
Oldin bilishingiz kerak: DI va IoC konteyner, Provider'lar va service'lar, Connection pooling, Env validatsiyasi va tipli config obyekti.
O'tgan darsda Nest bog'liqlikni token bo'yicha qidirishini ko'rdik va CONFIG tokenini qo'shdik. Bu darsda token ortida nima turishini belgilashning hamma usullarini o'rganamiz. Ulardan biri — fabrika — vazifalar-api ning omborini yaratadi.
1. Nega bu kerak?
Hozir vazifalar-api da ombor Nest'dan oldin yaratiladi:
// main.ts
const saqlash = await omborYarat(config, log);
// … keyin Nest'ga tayyor obyekt sifatida:
{ provide: VAZIFA_OMBORI, useValue: ombor }Bu ishlaydi, lekin ikki joyda hayot sikli boshqariladi. Ombor main.ts da tug'iladi va main.ts da yopiladi (saqlash.yop()). Nest esa uni faqat "qarzga" oladi. Natijada ikki muammo:
- Testlar ham,
main.tsham omborni o'zi yaratishi kerak — kod takrorlanadi. - Nest ilovani yopayotganda pool'ni yopish uning ishi emas —
main.tsbuni unutsa, ulanishlar ochiq qoladi.
25–27-qismlarda bu muammo yo'q edi, chunki Express'da konteyner umuman yo'q edi. Ombor ham, uni yopish ham bitta faylda — ishga-tushir.ts da — turardi va hammasini siz boshqarardingiz. Nest'ga o'tgach, obyektlarni yaratish konteynerga o'tdi. Lekin ombor hali eski joyda qoldi — shuning uchun "ikki xo'jayin" paydo bo'ldi.
Yaxshiroq yo'l: Nest'ga "omborni qanday yaratishni" aytish, tayyor obyektni emas. Bunday retsept — fabrika provider. Oshxona o'xshatishi: oshpazga tayyor osh emas, retsept beriladi. U kerak paytda o'zi pishiradi va idishlarni o'zi yuvadi.
2. To'rt shakl
2.1 Umumiy ko'rinish
Oddiy yozuv providers: [VazifalarService] — aslida qisqartma. To'liq shakli:
providers: [
{ provide: VazifalarService, useClass: VazifalarService },
]provide — token (kalit), use… — qiymat qanday olinishi. To'rt variant:
| Shakl | Qiymat | Tipik ishlatilishi |
|---|---|---|
useValue |
tayyor obyekt, son, satr | sozlama, tashqarida yaratilgan obyekt, testdagi soxta |
useClass |
Nest yaratadigan klass | muhitga qarab boshqa amalga oshirish |
useFactory |
funksiya natijasi | baza ulanishi, config'ga bog'liq obyekt |
useExisting |
boshqa token ortidagi o'sha obyekt | eski nomni saqlash, ikki interfeys — bitta obyekt |
2.2 useValue
{ provide: LOG, useValue: log },
{ provide: ILOVA_SOZLAMALARI, useValue: { tz: "Asia/Tashkent" } },Nest qiymatni yaratmaydi va o'zgartirmaydi, faqat beradi. vazifalar-api dagi BogliqliklarModule hammasi shu shaklda. Testlarda eng ko'p ishlatiladigan shakl ham shu: { provide: VAZIFA_OMBORI, useValue: soxtaOmbor }.
Bitta tuzoq: useValue ichidagi obyekt @Injectable() klass nusxasi bo'lsa ham, Nest uning bog'liqliklarini to'ldirmaydi — u tayyor deb hisoblanadi. Bog'liqlikni siz o'zingiz new bilan berasiz. Natijada konteynerdagi singleton bilan sizning nusxangiz — ikki xil obyekt.
Demo loyihada shuni tekshirdik (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0). Hisobot klassi Soat ga tayanadi. Bir token ostida uni useClass bilan, ikkinchisida useValue: new Hisobot(new Soat()) bilan berdik. Keyin har biridagi soat konteynerdagi Soat bilan bir xilmi (===) — shuni chiqardik:
{ provide: "HISOBOT_VALUE", useValue: new Hisobot(new Soat()) },
{ provide: "HISOBOT_CLASS", useClass: Hisobot },useClass: Hisobot (09:00) true
useValue: Hisobot (09:00) falseNima bo'ldi? Ikkalasi ham ishladi — sarlavha bir xil. Farq oxirgi so'zda. Birinchi qatorda Nest Soat ni o'zi topdi va o'sha singleton'ni berdi (true). useValue da esa Soat ni biz yaratdik — bu konteynerdagidan boshqa, ikkinchi nusxa (false). Soat holat saqlasa (kesh, hisoblagich, pool), ikki nusxa bir-birini ko'rmaydi. Xato chiqmaydi, faqat ma'lumot "ikkiga bo'linadi" — bunday xatoni topish qiyin.
2.3 useClass
{
provide: VAZIFA_OMBORI,
useClass: process.env["SAQLASH"] === "xotira"
? XotiraOmbori
: PgOmbori,
}Token bitta, klass — tanlov bo'yicha. 25-qismda xuddi shu tanlov omborYarat ichida oddiy if bilan yozilgan edi: shartga qarab new XotiraOmbori() yoki new PgOmbori(pool). Bu yerda farq bitta: new ni siz emas, Nest qiladi. Nest tanlangan klassni o'zi yaratadi va uning constructor bog'liqliklarini to'ldiradi. Muhim farq useValue dan: bu yerda klassning o'z DI zanjiri ishlaydi.
Qachon kerak? Bitta interfeysning bir nechta amalga oshirilishi bo'lganda: xotira va Postgres ombori, haqiqiy va soxta SMS jo'natuvchi. Lekin tanlov process.env ga bevosita qarasa — config tekshiruvi chetlab o'tiladi. Shuning uchun vazifalar-api tanlovni fabrika ichida, tekshirilgan config bo'yicha qiladi.
2.4 useFactory + inject
{
provide: VAZIFA_OMBORI,
useFactory: (config: Config, log: Log) => yarat(config, log),
inject: [CONFIG, LOG],
}useFactory — oddiy funksiya, uning qaytargani token qiymati bo'ladi. inject — funksiya argumentlari uchun tokenlar ro'yxati, tartib bilan: birinchi token — birinchi argument. Bu @Inject(...) ning fabrika uchun shakli.
Fabrika ham singleton: Nest uni bir marta chaqiradi va natijani saqlaydi. Kanon tajribasida fabrika ikki marta so'ralganda ham bir marta chaqirildi (pastdagi chiqishda 1).
inject tartibi adashsa nima bo'ladi? Demo loyihada inject: [LOG, CONFIG] yozdik, fabrika esa (config, log) kutadi:
{
provide: "SOZLAMA",
useFactory: (config: { tz: string }, log: unknown) =>
`tz=${config.tz}, log=${typeof log}`,
inject: [LOG, CONFIG],
}inject almashgan: tz=undefined, log=objectNest hech narsa demadi. config o'rniga log obyekti keldi, unda tz yo'q — undefined. TypeScript ham ushlamadi: inject — oddiy tokenlar massivi, u fabrika parametrlarining turini bilmaydi. Shuning uchun inject ni fabrika parametrlari bilan bir qatorda, bir tartibda yozing va fabrikani kichik tuting.
Tekshirib ko'ring:
useValue: new Hisobot(new Soat())dagi "ikki nusxa" muammosini fabrika bilan qanday yechasiz?
Javob
{ provide: "HISOBOT", useFactory: (s: Soat) => new Hisobot(s), inject: [Soat] }. Endi Soat ni biz emas, Nest beradi — konteynerdagi o'sha singleton. new baribir sizning kodingizda, lekin bog'liqlik inject orqali keladi. Eng sodda yo'l esa useClass: Hisobot (yoki shunchaki providers: [Hisobot]): klassning bog'liqliklarini Nest o'zi to'ldiradi. Fabrika faqat yaratishda qo'shimcha kod kerak bo'lsa foydali.
inject dagi tokenni ixtiyoriy qilish mumkin: inject: [{ token: XABARCHI, optional: true }] — topilmasa undefined. Bu @Optional() ning fabrika uchun shakli.
2.5 useExisting
{ provide: "OMBOR_ALIAS", useExisting: OMBOR }Taxallus (alias) — yangi obyekt yaratilmaydi, ikki token bitta nusxaga ishora qiladi. Masalan, eski kod "ombor" satr tokenini so'raydi, yangi kod esa VAZIFA_OMBORI ni — ikkalasi bitta omborni olsin. useValue: ombor dan farqi: useExisting haqiqiy provider'ga ishora qiladi va uning hayot siklini (yaratilish vaqti, scope) meros oladi.
Tekshirib ko'ring:
{ provide: A, useClass: Soat }va{ provide: B, useClass: Soat }—AvaBbitta nusxami?{ provide: B, useExisting: A }bo'lsa-chi?
Javob
Birinchi holatda — ikki nusxa: har useClass yozuvi o'z provider'i, Nest har biriga alohida new Soat() qiladi. Modullar darsidagi "ikki joyda providers" tuzog'ining kichik shakli. useExisting bilan esa bitta nusxa — B shunchaki A ning ikkinchi nomi.
3. Kanon tajribasi: hamma shakl bir joyda
Kanon beshta tokenni bir modulda ro'yxatdan o'tkazdi (kanon tajribasi, Vitest muhitida). Kod qisqartirilgan:
@Module({
providers: [
Soat,
{ provide: OMBOR, useValue: { ol: () => "useValue" } },
{
provide: "OMBOR_KLASS",
useClass: class implements Ombor {
ol() { return "useClass"; }
},
},
{
provide: "OMBOR_FABRIKA",
useFactory: (s: Soat) => {
fabrikaChaqiruv++;
return { ol: () => `useFactory (${s.hozir()})` };
},
inject: [Soat],
},
{
provide: "OMBOR_ASYNC",
useFactory: async () => {
await new Promise((r) => setTimeout(r, 50));
return { ol: () => "async useFactory (50 ms kutildi)" };
},
},
{ provide: "OMBOR_ALIAS", useExisting: OMBOR },
TokenliXizmat,
],
})
class Turlar {}Chiqishda uch narsaga qarang: birinchi qatordagi vaqt (ms), har token yonidagi qiymat va oxirgi ikki qatordagi true va 1:
5) custom provider'lar (kontekst 56 ms — async fabrika kutildi):
Symbol(OMBOR) → useValue
OMBOR_KLASS → useClass
OMBOR_FABRIKA → useFactory (2026-10-27T00:00:00.000Z)
OMBOR_ASYNC → async useFactory (50 ms kutildi)
OMBOR_ALIAS → useValue
useExisting bir xil obyekt: true; fabrika necha marta chaqirildi (singleton): 1
@Optional() @Inject("YOQ") → undefinedChiqishni o'qiymiz:
- 56 ms. Kontekst yaratish (
createApplicationContext) 56 ms oldi — shundan 50 ms async fabrikaningsetTimeouti. Nest Promise'ni kutdi: ilova u tayyor bo'lgunicha ko'tarilmadi. Bu — async provider'ning asosiy xususiyati. OMBOR_ALIAS → useValue— taxallusOMBORning o'zini berdi.true— bir xil obyekt (===).1— fabrika ikki marta so'ralgan, lekin bir marta chaqirilgan: singleton.undefined—@Optional()li token ro'yxatda yo'q, xato o'rnigaundefined.createApplicationContext— HTTP serversiz Nest ilovasi: faqat DI konteyner. Test va skriptlar uchun qulay; batafsil — Standalone ilova darsida.
flowchart LR
T1["CONFIG"] -->|useValue| V["tayyor obyekt"]
T2["SAQLASH"] -->|"useFactory (async)"| F["omborYarat(config, log)"]
F -. "inject" .-> T1
T3["VAZIFA_OMBORI"] -->|useFactory| G["saqlash.ombor"]
G -. "inject" .-> T2
T4["OMBOR_ALIAS"] -->|useExisting| T3Diagramma loyiha qadamidagi zanjirni ko'rsatadi: punktir — inject (fabrika nimani so'raydi), yorliqli strelka — provider shakli.
4. JS modelida provider turlari
To'rt shaklni kichik konteynerda takrorlaymiz — brauzerda ishlaydi. ol(token) provider shakliga qarab qiymatni oladi; async fabrikani kutadi; natijani saqlaydi (singleton). Nest kabi, hamma provider ishga tushishda yaratiladi:
const kut = (ms) => new Promise((r) => setTimeout(r, ms));
async function konteynerYarat(providerlar) {
const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
const tayyor = new Map();
async function ol(token) {
if (tayyor.has(token)) return tayyor.get(token);
const p = tavsif.get(token);
if (!p) throw new Error(`${String(token)} topilmadi`);
const bogliq = (r = []) => Promise.all(r.map(ol));
let q;
if ("useValue" in p) {
q = p.useValue;
} else if ("useClass" in p) {
q = new p.useClass(...await bogliq(p.kerak));
} else if ("useFactory" in p) {
q = await p.useFactory(...await bogliq(p.inject));
} else {
q = await ol(p.useExisting);
}
tayyor.set(token, q);
return q;
}
// Nest kabi: hammasi ishga tushishda yaratiladi
for (const t of tavsif.keys()) await ol(t);
return (t) => tayyor.get(t);
}
class XotiraOmbori { tur = "xotira"; }
class PgOmbori { tur = "postgres"; }
let fabrika = 0;
const CONFIG = Symbol("CONFIG");
const SAQLASH = Symbol("SAQLASH");
const OMBOR = Symbol("OMBOR");
const ol = await konteynerYarat([
{ provide: CONFIG, useValue: { SAQLASH: "xotira" } },
{
provide: SAQLASH,
inject: [CONFIG],
useFactory: async (config) => {
fabrika++;
await kut(50); // baza ulanishi
const K = config.SAQLASH === "xotira" ? XotiraOmbori : PgOmbori;
return { ombor: new K() };
},
},
{ provide: OMBOR, inject: [SAQLASH], useFactory: (s) => s.ombor },
{ provide: "OMBOR_TAXALLUS", useExisting: OMBOR },
]);
console.log(ol(OMBOR).tur);
console.log(ol("OMBOR_TAXALLUS") === ol(OMBOR));
console.log(`fabrika: ${fabrika} marta`);Konsolda:
xotira
true
fabrika: 1 martaSAQLASH fabrikasi ikki joydan so'ralgan (to'g'ridan va OMBOR orqali), lekin bir marta ishladi. konteynerYarat qaytgan paytda hamma qiymat tayyor — await kut(50) tugagan. Model soddalashtirilgan: unda modul chegarasi va aylanma bog'liqlik tekshiruvi yo'q, useClass bog'liqliklari esa metama'lumotdan emas, kerak maydonidan olinadi.
5. Async fabrika: ilova kutadi
5.1 Nima uchun async
Baza ulanishi asinxron: pool yaratiladi, birinchi so'rov yuboriladi, javob kutiladi. omborYarat ham async — 27-qismda u EGASI_ID ni bazadan tekshiradi va migratsiya holatini o'qiydi (fail-fast).
Async fabrika provider bilan bu tekshiruvlar Nest bootstrap ichida bo'ladi. Fabrika rad etilgan Promise qaytarsa (baza yo'q, EGASI_ID noto'g'ri), NestFactory.create ham rad etiladi — ilova ishga tushmaydi. 27-qismdagi xulq saqlanadi, faqat joyi o'zgaradi.
5.2 Biz o'lchadik
Loyiha qadamidagi provider'larni (pastda) alohida sinov ilovasida ishlatdik: CONFIG va LOG ni beradigan kichik global modul + yangi OmborModule (biz ishga tushirdik: NestJS 12.2.0, Node 24.21.0, PostgreSQL 17 — o'z vaqtinchalik klasterimiz, kanon sxemasi bilan). Uch xil config bilan uch marta ishga tushirdik. Har qatorda qaysi ombor kelganiga va qavs ichidagi vaqtga qarang:
xotira: XotiraOmbori (27 ms)
postgres: PgOmbori (132 ms)
postgres EGASI_ID=999: XATO — EGASI_ID=999 foydalanuvchilar jadvalida yo'qSAQLASH=xotira— fabrikaXotiraOmboriberdi, kontekst 27 ms da tayyor.SAQLASH=postgres—PgOmbori, 132 ms: pool ochildi,EGASI_IDtekshirildi, migratsiya holati o'qildi. Nest shuni kutdi.EGASI_ID=999— fabrika rad etildi,createApplicationContextham rad etildi. Xabar — 27-qismdagiomborYaratning o'zi.
Vaqtlar mashinaga bog'liq — sizda boshqa raqam chiqadi. Muhimi nisbat: async fabrika ilova ishga tushishini resurs tayyor bo'lguncha ushlab turadi.
Diqqat: Async fabrika ichidagi uzoq ish ilovani ham ushlab turadi. Baza 30 s javob bermasa, ilova 30 s ko'tarilmaydi va hech narsa yozmaydi.
omborYaratdagiconnectionTimeoutMillis: 5_000(Connection pooling) shuning uchun muhim: baza javob bermasa, 5 s da aniq xato.
Tekshirib ko'ring: Nega
VAZIFA_OMBORIalohida fabrika ((s) => s.ombor)?SAQLASHning o'ziniVAZIFA_OMBORIqilsa bo'lmaydimi?
Javob
omborYarat { ombor, yop } qaytaradi — ombor va uni yopish funksiyasi. Xizmatlarga faqat ombor kerak (VazifaOmbori interfeysi), yop esa ilovani yopayotgan kodga. Ikki token ikki iste'molchi uchun: VAZIFA_OMBORI — xizmatlarga, SAQLASH — yopuvchiga (keyinroq Lifecycle hook'lar darsida). Shunda xizmat tasodifan yop() ni chaqira olmaydi — u bunday metodni ko'rmaydi.
6. Qaysi shaklni tanlash
6.1 Uch savol
Yangi provider yozayotganda ketma-ket uch savol bering:
- Qiymat Nest'dan oldin tayyormi? (sozlama obyekti, test soxtasi, tashqarida yaratilgan logger) —
useValue. Nest uni yaratmaydi va yopmaydi; hayot sikli sizda. - Qiymat klassmi va uning bog'liqliklarini Nest to'ldirsinmi? —
useClass(yoki oddiyproviders: [Klass]). Tanlov kerak bo'lsa (ikki amalga oshirish), tanlovni config bo'yicha qiling. - Qiymatni yaratish uchun kod kerakmi (ulanish ochish,
await, boshqa provider'dan hisoblash)? —useFactory+inject.
useExisting — alohida holat: yangi qiymat emas, mavjudiga ikkinchi nom.
Uch savolni bitta misolda sinaymiz. 29-qismdagi SMS jo'natuvchi Nest'ga ko'chadi. SMS_PROVAYDER=eskiz bo'lsa haqiqiy provayder kerak, u esa ishga tushishda token oladi (await). Testda esa yuborilgan xabarlarni massivga yozadigan soxta obyekt yetadi. Bo'shliqlarni to'ldiring:
- Prodakshndagi provayder: avval token so'raladi, keyin obyekt beriladi —
- Unga config kerak — tokenlar ro'yxati maydoni —
- Testdagi soxta obyekt —
Birinchi javob — useFactory: "yaratish uchun kod va await kerakmi?" degan savolga "ha". Ikkinchisi — inject: [CONFIG]. Uchinchisi — useValue: soxta obyekt Nest'dan oldin tayyor, unga bog'liqlik kerak emas.
6.2 Testda almashtirish
Custom provider'larning eng ko'p foydasi — testda. Haqiqiy xizmat o'rniga soxtasini o'sha token bilan beramiz:
const modul = await Test.createTestingModule({
controllers: [VazifalarController],
providers: [
{ provide: VazifalarService, useValue: soxtaXizmat },
{ provide: ILOVA_SOZLAMALARI, useValue: { tz: "UTC" } },
],
}).compile();Controller hech narsani sezmaydi: u VazifalarService tokenini so'raydi va javobda soxtaXizmat ni oladi. Klass tokeni bilan useValue — "shu klass o'rniga mana bu obyekt". To'liq test naqshlari — NestJS unit testlari darsida.
6.3 Provider'lar qayerda yashaydi
Kichik modulda provider'lar to'g'ridan @Module({ providers: [...] }) ichida. Ro'yxat uzunlashsa, katta loyihalarda alohida fayl ochiladi:
// ombor.providers.ts
export const omborProviderlari: Provider[] = [
{ provide: SAQLASH, useFactory: …, inject: [CONFIG, LOG] },
{ provide: VAZIFA_OMBORI, useFactory: …, inject: [SAQLASH] },
];Modul esa providers: [...omborProviderlari] bilan oladi. Bu faqat tartib uchun — Nest uchun farqi yo'q. vazifalar-api da provider'lar kam, shuning uchun ular modul faylida.
7. Ko'p uchraydigan xatolar
| Belgi | Sabab | Davosi |
|---|---|---|
Fabrika argumentlari undefined |
inject unutilgan yoki tartibi xato |
inject ni argumentlar tartibida yozing |
useValue dagi xizmatning bog'liqliklari undefined yoki "boshqa nusxa" |
Nest useValue ni to'ldirmaydi |
useClass yoki useFactory + inject |
Fabrika argumenti boshqa obyekt (tz=undefined) |
inject tartibi parametrlar tartibiga mos emas |
inject ni parametrlar bilan yonma-yon yozing |
Ikki useClass — ikki nusxa |
har yozuv alohida provider | useExisting taxallus |
| Ilova uzoq ko'tarilmaydi, log yo'q | async fabrika osilib qoldi | resurs timeout'i (connectionTimeoutMillis) |
| Fabrika har so'rovda chaqiriladi deb kutish | fabrika singleton — bir marta | har so'rov uchun — scope (Provider scope'lari) |
8. Mashqlar
Mashqlar kurs/mashqlar/30/09-custom-provider/ da.
1-mashq (oson): qaysi shakl?
- Testda soxta ombor berish —
SMS_PROVAYDER=eskizbo'lsaEskizSms, aks holdaSoxtaSmsklassi —- Redis ulanishini ochib, keyin berish —
- Eski
"ombor"tokenini yangiVAZIFA_OMBORIga bog'lash — useFactoryargumentlari qaysi maydonda? —
Yechim
- useValue — tayyor soxta obyekt.
- useClass — tanlov bo'yicha klass; Nest uni o'zi yaratadi.
- useFactory — async:
await redis.connect(). - useExisting — taxallus, bitta nusxa.
- inject — tokenlar ro'yxati, argumentlar tartibida.
2-mashq (o'rta): muhitga qarab provider
omborProvideri(saqlash) funksiyasini yozing: "xotira" bo'lsa { provide: OMBOR, useClass: XotiraOmbori }, "postgres" bo'lsa { provide: OMBOR, useClass: PgOmbori, kerak: [POOL] }, boshqa qiymatda Error("noma'lum saqlash: …"). Darsdagi konteynerYarat bilan ikkala holatni sinang (POOL — useValue: { nomi: "pool" }), oxirida noma'lum qiymatni. Ishora: switch (switch) yoki obyekt-jadval.
Yechim
async function konteynerYarat(providerlar) {
const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
const tayyor = new Map();
async function ol(token) {
if (tayyor.has(token)) return tayyor.get(token);
const p = tavsif.get(token);
if (!p) throw new Error(`${String(token)} topilmadi`);
const args = await Promise.all((p.kerak ?? []).map(ol));
const q = "useValue" in p ? p.useValue : new p.useClass(...args);
tayyor.set(token, q);
return q;
}
for (const t of tavsif.keys()) await ol(t);
return (t) => tayyor.get(t);
}
class XotiraOmbori { tur = "xotira"; }
class PgOmbori {
constructor(pool) { this.tur = `postgres (${pool.nomi})`; }
}
const OMBOR = Symbol("OMBOR");
const POOL = Symbol("POOL");
function omborProvideri(saqlash) {
switch (saqlash) {
case "xotira":
return { provide: OMBOR, useClass: XotiraOmbori };
case "postgres":
return { provide: OMBOR, useClass: PgOmbori, kerak: [POOL] };
default:
throw new Error(`noma'lum saqlash: ${saqlash}`);
}
}
for (const s of ["xotira", "postgres", "mongo"]) {
try {
const ol = await konteynerYarat([
{ provide: POOL, useValue: { nomi: "pool" } },
omborProvideri(s),
]);
console.log(s, "→", ol(OMBOR).tur);
} catch (e) {
console.log(s, "→", e.message);
}
}Konsolda:
xotira → xotira
postgres → postgres (pool)
mongo → noma'lum saqlash: mongoNoma'lum qiymat konteyner yaratilishidan oldin xato berdi — fail-fast. Haqiqiy vazifalar-api da bu tekshiruv bundan ham oldin: configYukla SAQLASH ni Zod enum bilan tekshiradi, noto'g'ri qiymatda ilova 78 kodi bilan to'xtaydi.
3-mashq (qiyin): ixtiyoriy inject
Konteynerga inject ichida obyekt yozishni qo'shing: { token, optional: true }. Bunday token topilmasa — undefined berilsin, oddiy token topilmasa — eski xato. Sinov: BILDIRISHNOMA fabrikasi [LOG, { token: XABARCHI, optional: true }] oladi va xabarchi ? "webhook bilan" : "webhooksiz" qaytaradi. Avval XABARCHI siz, keyin u bilan. Ishora: typeof k === "object" — obyekt shakli; tavsif.has(k.token).
Yechim
async function konteynerYarat(providerlar) {
const tavsif = new Map(providerlar.map((p) => [p.provide, p]));
const tayyor = new Map();
async function ol(token) {
if (tayyor.has(token)) return tayyor.get(token);
const p = tavsif.get(token);
if (!p) throw new Error(`${String(token)} topilmadi`);
const args = await Promise.all((p.inject ?? []).map((k) => {
if (typeof k === "object" && k.optional) {
return tavsif.has(k.token) ? ol(k.token) : undefined;
}
return ol(k);
}));
const q = "useValue" in p
? p.useValue
: await p.useFactory(...args);
tayyor.set(token, q);
return q;
}
for (const t of tavsif.keys()) await ol(t);
return (t) => tayyor.get(t);
}
const LOG = Symbol("LOG");
const XABARCHI = Symbol("XABARCHI");
const BILDIRISHNOMA = Symbol("BILDIRISHNOMA");
const bildirishnoma = {
provide: BILDIRISHNOMA,
inject: [LOG, { token: XABARCHI, optional: true }],
useFactory: (log, xabarchi) =>
`${log.nomi}, ${xabarchi ? "webhook bilan" : "webhooksiz"}`,
};
const log = { provide: LOG, useValue: { nomi: "pino" } };
const ol1 = await konteynerYarat([log, bildirishnoma]);
console.log(ol1(BILDIRISHNOMA));
const ol2 = await konteynerYarat([
log, bildirishnoma, { provide: XABARCHI, useValue: () => {} },
]);
console.log(ol2(BILDIRISHNOMA));Konsolda:
pino, webhooksiz
pino, webhook bilanNest'da xuddi shu yozuv ishlaydi: inject: [LOG, { token: XABARCHI, optional: true }]. vazifalar-api boshqa yo'lni tanladi — XABARCHI har doim ro'yxatda, qiymati esa null bo'lishi mumkin (Provider'lar darsi). Ikkalasi to'g'ri; muhimi — loyiha ichida bitta usul.
9. Loyiha qadami: ombor fabrikasi
9.1 Reja
Bu dars ikki ish qiladi:
omborYaratalohida faylga ko'chadi —ombor-yarat.ts.- Yangi
ombor.module.ts— config bo'yicha omborni yaratadigan provider'lar.
Yangi modul hali ulanmaydi: ombor main.ts da yaratilib, BogliqliklarModule orqali berilishda davom etadi. Ikkita bir xil token (VAZIFA_OMBORI) daraxtda bo'lsa, ulardan qaysi biri ishlashi chalkash bo'ladi. Almashtirish — Dynamic modullar darsida, bitta qadamda. Bu darsda faqat "retsept" yoziladi va alohida sinaladi; ilovaning xulqi esa o'zgarmaydi. Kichik qadam: biror narsa buzilsa, aybdorni qidirish oson.
9.2 ombor-yarat.ts
27-qismdagi ishga-tushir.ts ning yarmi — OmborLogi turi, migratsiyaniTekshir, Saqlash turi, omborYarat va asosiyOmbor — yangi faylga o'zgarishsiz ko'chadi. Faylning boshi:
// ombor-yarat.ts — config bo'yicha ombor (xotira, fayl, pg, prisma,
// + Redis kesh) va uni yopish. 27-qism ishga-tushir.ts dan ajratildi:
// Nest OmborModule.forRootAsync() fabrikasi shu funksiyani chaqiradi
// (ilova.ts ↔ modul aylanma importi bo'lmasin)
import type { Config } from "./config.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
// … qolgan importlar: fayl, pg, prisma, redis, kesh omborlariNega alohida fayl? Fabrika omborYarat ni chaqiradi. Agar uni eski joyidan — ishga-tushir.ts dan — import qilsak, zanjir yopiladi: ilova.ts → app.module.ts → ombor.module.ts → ishga-tushir.ts → ilova.ts. Bu — fayllar orasidagi aylanma import. ESM'da u ba'zan ishlaydi, ba'zan build'dan keyin yiqiladi; kanon buni o'lchagan, natijasi Circular dependency darsida. Eng yaxshi yechim — aylanmani umuman qurmaslik.
ishga-tushir.ts da eski joy saqlanadi — 27-qism testlari (pg-ombor.test, prisma-ombor.test) omborYarat ni shu yerdan import qiladi:
// ishga-tushir.ts — Nest ilovasi, http server timeout'lari va
// tinglash
import type { Server } from "node:http";
import type { AddressInfo } from "node:net";
import type {
NestExpressApplication,
} from "@nestjs/platform-express";
import { ilovaYarat } from "./ilova.ts";
import type { Bogliqliklar } from "./ilova.ts";
import { XotiraOmbori } from "./vazifalar/xotira-ombor.ts";
// 27 testlari (pg-ombor, prisma-ombor) omborYarat'ni shu fayldan
// import qiladi — eski yo'l saqlanadi
export { omborYarat } from "./ombor-yarat.ts";
export type { OmborLogi, Saqlash } from "./ombor-yarat.ts";Faylning qolgan qismi (Ishlayotgan turi va ishgaTushir) o'zgarmaydi. export { … } from — qayta eksport: fayl o'zi ishlatmaydi, faqat uzatadi (ESM import). Pg, Prisma, Redis importlari bu fayldan ketdi — ular endi ombor-yarat.ts da.
9.3 ombor.module.ts
// ombor.module.ts — VAZIFA_OMBORI ni config bo'yicha yaratadigan
// provider'lar: ASYNC fabrika — Nest bootstrap paytida Promise'ni
// kutadi (baza ulanishi, migratsiya tekshiruvi — fail-fast).
// Hozircha AppModule'ga ulanmagan
import { Global, Module } from "@nestjs/common";
import { omborYarat } from "./ombor-yarat.ts";
import type { Saqlash } from "./ombor-yarat.ts";
import type { Config } from "./config.ts";
import type { Log } from "./umumiy/log.ts";
import { CONFIG, LOG, VAZIFA_OMBORI } from "./tokenlar.ts";
const SAQLASH = Symbol("SAQLASH");
@Global()
@Module({
providers: [
// async fabrika: Nest Promise'ni kutadi; xato bo'lsa — ilova
// ishga TUSHMAYDI (EGASI_ID yo'q, baza yo'q, migratsiya orqada)
{
provide: SAQLASH,
useFactory: (config: Config, log: Log): Promise<Saqlash> =>
omborYarat(config, log),
inject: [CONFIG, LOG],
},
{
provide: VAZIFA_OMBORI,
useFactory: (s: Saqlash) => s.ombor,
inject: [SAQLASH],
},
],
exports: [VAZIFA_OMBORI],
})
export class OmborModule {}Har qatorni o'qing:
SAQLASH— eksport qilinmagan token, faylning ichida. Tashqaridan uni hech kim so'ray olmaydi — faqat shu modul.- Birinchi fabrika:
inject: [CONFIG, LOG]— o'tgan darsda qo'shganCONFIGtokeni shu yerda ishga tushdi. FabrikaPromise<Saqlash>qaytaradi — Nest kutadi. - Ikkinchi fabrika:
inject: [SAQLASH]— birinchisining natijasidan faqat omborni oladi. exports: [VAZIFA_OMBORI]— tashqariga faqat ombor.@Global()— xizmatlar uni import qilmasdan oladi, hozirBogliqliklarModuledan olgandek.
Bu provider'lar «Biz o'lchadik» bo'limidagi sinovda ishlatilgan — XotiraOmbori, PgOmbori va EGASI_ID xatosi shu moduldan.
9.4 Tekshiruv
npm run tip
npm run lint
npm testBiz kanon nusxasida yurgizdik: tip 0, lint 0, Vitest — o'tgan darsdagidek (71 o'tdi, bazasiz 5 fayl SKIP). Modul ulanmagani uchun ilova xulqi o'zgarmadi. Hali commit qilmang — holat commit'i Reflector va DiscoveryService darsida.
10. Real ishda
TypeOrmModule.forRootAsync({ useFactory, inject: [ConfigService] })— Nest ekotizimidagi deyarli har modul (JwtModule,BullModule,CacheModule) shu naqsh: async fabrika + config'dan olish.- Testlarda
Test.createTestingModule(...).overrideProvider(VAZIFA_OMBORI).useValue(soxta)—useValuening eng ko'p uchraydigan joyi (NestJS unit testlari). - Prodakshnda async fabrika "ilova tayyorlik" (readiness) bilan bog'lanadi: ilova faqat baza ulangach portni ochadi, orkestrator esa shundan keyin trafik yuboradi (Health check'lar).
- Kod review'da
useValue: new Xizmat(new Bogliqlik())ko'rinsa, birinchi savol: "bu bog'liqlik konteynerda ham bormi?". Bor bo'lsa — ikki nusxa yashayapti,useClassyokiinjectli fabrika taklif qilinadi. Uzuninjectro'yxati ham ogohlantiruvchi belgi: fabrika juda ko'p ish qilyapti. - Intervyu: "Custom provider turlarini ayting", "
useClassvauseExistingfarqi?", "Async provider nima va ilova uni qanday kutadi?".
Xulosa
- Provider obyekt shaklida:
{ provide: TOKEN, useValue | useClass | useFactory | useExisting }. useValue— tayyor qiymat (Nest to'ldirmaydi),useClass— tanlangan klass (Nest yaratadi va to'ldiradi).useFactory+inject— funksiya natijasi;injecttartibi — argumentlar tartibi;{ token, optional: true }mumkin.useExisting— taxallus, bitta nusxa; ikkiuseClassesa ikki nusxa.useValueganewbilan yaratilgan xizmat konteynerdagi singleton'ni ko'rmaydi;injecttartibini esa na Nest, na TypeScript tekshiradi.- Async fabrika — Nest Promise'ni kutadi; rad etilsa, ilova ishga tushmaydi (fail-fast).
vazifalar-apida ombor fabrikasi yozildi (OmborModule),omborYaratalohida faylga ko'chdi — aylanma importsiz.
Keyingi dars: Provider scope'lari — singleton'dan tashqari REQUEST va TRANSIENT, ularning "yuqishi" va narxi kanon o'lchovi bilan.
Manbalar
- NestJS hujjati — Custom providers (
useValue,useClass,useFactory,useExisting, optional inject): https://docs.nestjs.com/fundamentals/custom-providers - NestJS hujjati — Asynchronous providers: https://docs.nestjs.com/fundamentals/async-providers
- Kanon tajribasi (IlmHamroh, 2026-10-27): beshta provider shakli bir kontekstda va async fabrika kutilishi. NestJS 12.2.0, Vitest muhitida.
OmborModuleprovider'lari sinovi (xotira, postgres,EGASI_IDxatosi) — biz ishga tushirdik (2026-11-02, NestJS 12.2.0, Node 24.21.0, PostgreSQL 17).
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!