IlmHamroh
JavaScript Full-stack/10-qism. JavaScript chuqur59/65-dars19 daqiqa
Mundarija (34)

JavaScript using: resurslarni aniq boshqarish va Symbol.dispose

Qisqacha: using nom = resurs; — const ga o'xshash e'lon, faqat blok tugaganda JavaScript resursning [Symbol.dispose]() metodini o'zi chaqiradi: xato chiqsa ham, return bo'lsa ham. Bu try...finally dagi "yopishni unutmang" ishini avtomatlashtiradi. Bir nechta using teskari tartibda yopiladi. Node 24, Chrome 134+ va Firefox 141+ da ishlaydi, Safari'da hali yo'q — Baseline emas. Shuning uchun bugun uni Node kodida ishlatamiz.

Bu darsda

  • "Resurs" nima ekanini va uni yopish nega muhimligini tushuntira olasiz.
  • Symbol.dispose bilan o'z "yopiladigan" obyektingizni yasaysiz va uni using bilan ishlatasiz.
  • Bir nechta resurs qaysi tartibda yopilishini va xato chiqqanda nima bo'lishini (SuppressedError) bilasiz.
  • DisposableStack bilan oddiy funksiyalarni ham avtomatik tozalashga qo'shasiz.
  • using bugun qayerda ishlaydi, qayerda yo'q — halol ro'yxatni bilasiz.

Oldin bilishingiz kerak: try / catch / finally, Xato strategiyasi, Well-known symbols, JS'da dizayn naqshlari.

1. Nega bu kerak?

1.1 Ochilgan narsa yopilishi kerak

«Bahor» kassasida har kuni ertalab Otabek smena ochadi, kechqurun yopadi. Yopilmagan smena — muammo: ertangi kun hisobi kechagisi bilan aralashadi. Dasturda ham shunday narsalar ko'p. Resurs (resource) — dastur vaqtincha egallab oladigan va ishi tugagach albatta qaytarishi (yopishi) kerak bo'lgan narsa. Xuddi kutubxonadan olingan kitob kabi: o'qib bo'lgach, qaytarasiz. Misollar:

  • ochilgan fayl — yopilmasa, boshqa dastur unga yoza olmaydi;
  • taymer (setInterval) — to'xtatilmasa, abadiy ishlaydi va Node dasturi tugamaydi;
  • hodisaga obuna — bekor qilinmasa, kerak bo'lmagan funksiya chaqirilaveradi (JS'da dizayn naqshlari darsidagi obuna aynan bekor qilish funksiyasini qaytarardi);
  • vaqtinchalik papka — o'chirilmasa, disk to'lib boradi.

Qolip doim bir xil: och → ishlat → yop. try / catch / finally darsida "yop" qismini finally ga qo'yishni o'rgandik — xato bo'lsa ham yopilsin deb:

js
function smenaOch(kassir) {
  console.log(`${kassir}: smena ochildi`);
  return { yop: () => console.log(`${kassir}: smena yopildi`) };
}

const smena = smenaOch("Otabek");
try {
  console.log("Chek: osh, 35 000");
  JSON.parse("{");
} catch {
  console.log("Buzuq buyurtma o'tkazib yuborildi");
} finally {
  smena.yop();
}

Konsolda:

text
Otabek: smena ochildi
Chek: osh, 35 000
Buzuq buyurtma o'tkazib yuborildi
Otabek: smena yopildi

Ishlaydi. Lekin uchta zaif joyi bor:

  1. Unutish oson. finally — dasturchining xotirasiga tayanadi. Bitta joyda unutildi — resurs "oqib ketdi".
  2. "Och" va "yop" bir-biridan uzoq. Orada 30 qator kod bo'lsa, ular juft ekanini ko'rish qiyin.
  3. Ko'p resurs — ko'p qavat. Ikki resurs — ichma-ich ikki try...finally, uchtasi — uch qavat. Kod o'ngga "qochib" ketadi.

1.2 G'oya

Mehmonxonadagi xona kartasini eslang. Eshik yonida karta uchun teshik bor. Kartani qo'ysangiz — chiroq yonadi. Chiqib ketayotganda kartani olsangiz, chiroq va konditsioner o'zi o'chadi. Siz "chiroqni o'chirishni unutmang" deb o'ylamaysiz — tizim buni chiqish bilan bog'lab qo'ygan.

using — const va let kabi yangi e'lon kalit so'zi. U ham shunday ishlaydi: resurs blokka ({ } orasidagi kodga) bog'lanadi. Blokdan chiqish — qanday yo'l bilan bo'lmasin — resursni yopadi. Xona kartasi kabi, buni ham o'zimiz eslab qolishimiz shart emas.

2. Symbol.dispose: "meni qanday yopish kerak"

2.1 Protokol

using bilan ishlatiladigan obyekt bitta shartga javob berishi kerak: uning [Symbol.dispose] nomli metodi bo'lsin. Bu metod — "meni yop".

Symbol.dispose — Well-known symbols darsidagi Symbol.iterator ning qarindoshi. Symbol.iterator "meni qanday aylanish kerak" deydi va for...of uni ishlatadi. Symbol.dispose "meni qanday yopish kerak" deydi va using uni ishlatadi. Bunday obyektni yopiladigan (disposable) obyekt deyishadi.

js
function smenaOch(kassir) {
  console.log(`${kassir}: smena ochildi`);
  return {
    kassir,
    [Symbol.dispose]() {
      console.log(`${kassir}: smena yopildi`);
    },
  };
}

{
  using smena = smenaOch("Otabek");
  console.log(`Chek: osh, kassir ${smena.kassir}`);
}
console.log("Blokdan chiqdik");

Konsolda:

text
Otabek: smena ochildi
Chek: osh, kassir Otabek
Otabek: smena yopildi
Blokdan chiqdik

[Symbol.dispose]() { ... } — kalit simvol bo'lgan metod (Shorthand va computed kalitlar). Biz uni hech qayerda chaqirmadik — } ga yetganda JavaScript chaqirdi.

2.2 using = try...finally

using hech qanday "sehr" emas. Yuqoridagi blok mohiyatan shuning qisqa yozuvi:

js
{
  const smena = smenaOch("Otabek");
  try {
    console.log(`Chek: osh, kassir ${smena.kassir}`);
  } finally {
    smena[Symbol.dispose]();
  }
}

Ya'ni finally ning hamma kafolati using da ham bor: xato chiqsa ham, return bo'lsa ham yopiladi.

js
function smenaOch(kassir) {
  return {
    [Symbol.dispose]() {
      console.log(`${kassir}: smena yopildi`);
    },
  };
}

function chekUr(matn) {
  using smena = smenaOch("Otabek");
  const buyurtma = JSON.parse(matn);
  return `Chek: ${buyurtma.taom}`;
}

console.log(chekUr('{"taom": "Manti"}'));
try {
  chekUr("{");
} catch (xato) {
  console.log("Ushlandi:", xato.name);
}

Konsolda:

text
Otabek: smena yopildi
Chek: Manti
Otabek: smena yopildi
Ushlandi: SyntaxError

Ikkala chaqiruvda ham smena yopildi. Birinchisida — return dan oldin (xuddi finally dagidek — natija qaytishidan oldin). Ikkinchisida — xato yuqoriga ketishidan oldin. Funksiya tanasi ham blok: using funksiya tugaganda ishlaydi.

Tekshirib ko'ring: using blok ichida yozilgan. Yopish qachon chaqiriladi — using qatoridan keyinmi yoki boshqa vaqtda?

Javob

Blok tugaganda — } ga yetganda yoki blokdan return, break yoki xato bilan chiqilganda. using qatorida resurs faqat ro'yxatga olinadi. Shuning uchun resurs blokning oxirigacha ishlatilishi mumkin.

3. Bir nechta resurs: teskari tartib

Choyxona ochilishi: avval kassa, keyin oshxona. Yopilishi-chi? Mantiqan teskari: avval oshxona (u kassaga hisobot beradi), keyin kassa. using aynan shunday qiladi — oxirgi ochilgan birinchi yopiladi:

"Stek" ustuniga qarang: resurslar xuddi call stack kabi ustma-ust yig'iladi va ustidan olinadi. Nega bu to'g'ri tartib? Ko'pincha keyingi resurs oldingisiga tayanadi: oshxona kassaga yozadi, fayl papka ichida turadi. Avval tayanganni yopish kerak, keyin tayanchni. Ichma-ich try...finally ham aynan shu tartibni beradi — using uni avtomatik saqlaydi.

4. using qoidalari

4.1 const ga o'xshaydi

using — blok ichida yashaydigan, qayta tayinlab bo'lmaydigan e'lon:

js
{
  using smena = { [Symbol.dispose]() {} };
  smena = null;
}
text
TypeError: Assignment to using variable.

Tarjimasi: "using o'zgaruvchisiga qiymat berish". Mantiqiy: agar qayta tayinlash mumkin bo'lsa, JavaScript qaysi obyektni yopishni bilmay qolardi. E'tibor bering: bu SyntaxError emas, TypeError — xato kod bajarilayotganda chiqadi, const dagi Assignment to constant variable. kabi (Stack trace).

4.2 Qiymat — yopiladigan obyekt yoki null

using ga oddiy son yoki [Symbol.dispose] siz obyekt berilsa — xato. Node 24 dagi haqiqiy xabarlar:

text
TypeError: An object is expected with `using` declarations
TypeError: Symbol(Symbol.dispose) is not a function

Birinchisi — using x = 5: "using e'lonida obyekt kutilgan". Ikkinchisi — using x = {}: obyekt bor, lekin [Symbol.dispose] funksiya emas (aslida — yo'q, undefined).

null va undefined esa ruxsat etilgan — ular uchun yopish shunchaki o'tkazib yuboriladi. Bu "resurs bo'lishi ham mumkin, bo'lmasligi ham" holati uchun qulay:

js
function jurnalOch(kerakmi) {
  if (!kerakmi) {
    return null;
  }
  return { [Symbol.dispose]: () => console.log("jurnal yopildi") };
}

{
  using jurnal = jurnalOch(false);
  console.log("jurnalsiz ishladik");
}

Konsolda:

text
jurnalsiz ishladik

4.3 Qayerda yozish mumkin

using — blok ichida: { }, funksiya tanasi, for tanasi. Shuningdek for (using x of ...) — har aylanishda element yopiladi. Faylning eng yuqori darajasida esa faqat modulda (Nega modullar): Node'dagi .mjs fayl yoki brauzerdagi <script type="module">. Oddiy brauzer skriptining yuqori darajasida Chrome 154 uni tanimaydi:

text
Uncaught SyntaxError: Unexpected identifier 'smena'

Qoida oddiy: using ni blok yoki funksiya ichida yozing — u hamma joyda ishlaydi va resurs qancha yashashini aniq ko'rsatadi.

5. Node 24 dagi tayyor resurslar

Node'ning ba'zi obyektlarida [Symbol.dispose] allaqachon bor — ularni o'zingiz o'rashingiz shart emas.

Diqqat: bu bo'limdagi ikki misol faqat Node 24 da ishlaydi. Saytdagi «Ishga tushir» kodni brauzerda bajaradi. U yerda setInterval obyekt emas, oddiy son qaytaradi va TypeError: An object is expected with using declarations chiqadi (Chrome 154 da sinadik). node:fs esa brauzerda umuman yo'q. Bu bloklarni kompyuteringizda .mjs faylga yozib, node bilan ishga tushiring.

Taymer. setInterval qaytargan obyekt yopilganda taymer to'xtaydi:

js
{
  using taymer = setInterval(() => console.log("tik"), 10);
  console.log("blok ichida");
}
console.log("taymer to'xtadi, dastur tugaydi");

Konsolda:

text
blok ichida
taymer to'xtadi, dastur tugaydi

Bitta ham "tik" chiqmadi: blok tugaganda taymer to'xtadi. using siz bu dastur abadiy "tik" yozib turardi — clearInterval unutilgan.

Vaqtinchalik papka. Node 24 da fs.mkdtempDisposableSync vaqtinchalik papka yaratadi. Yopilganda u papkani ichidagi fayllar bilan birga o'chiradi. fs va os — Node'ning fayl tizimi va operatsion tizim modullari, import qatorlarini Nega modullar darsida o'rganamiz, hozir natijaga qarang:

js
import fs from "node:fs";
import os from "node:os";

let yol;
{
  using papka = fs.mkdtempDisposableSync(`${os.tmpdir()}/bahor-`);
  yol = papka.path;
  fs.writeFileSync(`${yol}/chek.txt`, "Osh — 35 000 so'm");
  console.log(fs.readdirSync(yol)); // [ 'chek.txt' ]
}
console.log(fs.existsSync(yol)); // false

Blok ichida papkada fayl bor edi. Blokdan keyin papka umuman yo'q — using uni tozaladi. Sinov va vaqtinchalik hisobotlar uchun aynan shu kerak.

6. DisposableStack: hamma narsani yopishga qo'shish

6.1 Muammo: yopish — oddiy funksiya

Ko'p API'larda [Symbol.dispose] yo'q. "Yopish" ko'pincha oddiy funksiya: JS'da dizayn naqshlari darsidagi obuna "bekor qilish" funksiyasini qaytaradi, addEventListener uchun removeEventListener bor. Ularni using ga qanday berish mumkin?

DisposableStack — "yopishlar steki". U o'zi yopiladigan obyekt, ichiga esa istalgan tozalash ishini qo'shish mumkin:

Metod Nima qiladi
defer(fn) Yopilganda fn() ni chaqiradi
use(resurs) Yopiladigan obyektni qo'shadi va uni qaytaradi
adopt(qiymat, fn) Yopilganda fn(qiymat) — [Symbol.dispose] siz qiymat uchun
move() Hammasini yangi stekka ko'chiradi, eskisi bo'shaydi

To'rttasini birdan yodlash shart emas. Boshida defer yetarli — keyingi misol faqat shuni ishlatadi. Qolganlari «Tartib va move» bo'limida.

6.2 Vaqtinchalik obuna

Kuzatuvchiga vaqtincha jurnal ulaymiz — faqat blok davomida:

js
function kuzatuvchiYarat() {
  let tinglovchilar = [];
  return {
    obuna(tinglovchi) {
      tinglovchilar.push(tinglovchi);
      return () => {
        tinglovchilar = tinglovchilar.filter((t) => t !== tinglovchi);
      };
    },
    xabarBer(matn) {
      tinglovchilar.forEach((t) => t(matn));
    },
  };
}

const royxat = kuzatuvchiYarat();
{
  using stek = new DisposableStack();
  stek.defer(royxat.obuna((m) => console.log("Jurnal:", m)));
  royxat.xabarBer("Non olish qo'shildi");
}
royxat.xabarBer("Choy damlash qo'shildi");
console.log("Blokdan keyin jurnal jim");

Konsolda:

text
Jurnal: Non olish qo'shildi
Blokdan keyin jurnal jim

obuna bekor qilish funksiyasini qaytaradi — biz uni to'g'ridan-to'g'ri defer ga berdik. Blok tugadi → stek yopildi → bekor qilish chaqirildi → ikkinchi xabarni hech kim eshitmadi. "Obuna bo'ldim, lekin bekor qilishni unutdim" — xotira sizib chiqishining eng ko'p uchraydigan sababi (Closure tuzoqlari). DisposableStack uni blokka bog'ladi.

6.3 Tartib va move

Stekdagi ishlar ham teskari tartibda bajariladi, va bir marta yopilgan stekka yangi ish qo'shib bo'lmaydi:

js
const stek = new DisposableStack();
stek.defer(() => console.log("1-defer"));
stek.use({ [Symbol.dispose]: () => console.log("2-use") });
stek.adopt(35000, (narx) => console.log("3-adopt:", narx));

console.log(stek.disposed); // false
stek.dispose();
console.log(stek.disposed); // true

try {
  stek.defer(() => {});
} catch (xato) {
  console.log(xato.name); // ReferenceError
}

Konsolda:

text
false
3-adopt: 35000
2-use
1-defer
true
ReferenceError

dispose() — qo'lda yopish (using siz ham ishlatsa bo'ladi). Yopilgan stekka qo'shishga urinish — ReferenceError: Cannot call DisposableStack.prototype.defer on an already-disposed DisposableStack.

move() — murakkabroq holat uchun: funksiya bir nechta resurs ochadi va ularni chaqiruvchiga topshirmoqchi. Ichkarida using stek — o'rtada xato chiqsa, hammasi yopiladi. Hammasi muvaffaqiyatli bo'lsa, return stek.move() — resurslar yangi stekka o'tadi, eski stek esa bo'sh holda yopiladi. Endi ularni yopish chaqiruvchining using iga tegishli. Bu naqsh kutubxona kodlarida uchraydi; boshida faqat defer va use yetarli.

7. Ikki xato birdaniga: SuppressedError

Yopishning o'zi ham xato tashlashi mumkin. Agar blok xato bilan tugayotgan bo'lsa-yu, yopishda yana xato chiqsa-chi? try / catch darsida ko'rdik: finally dagi throw asl xatoni bosib ketadi. using buni boshqacha hal qiladi — ikkalasini ham saqlaydi:

js
function smenaOch(nom) {
  return {
    [Symbol.dispose]() {
      throw new Error(`${nom}: kassa qulfi ochilmadi`);
    },
  };
}

try {
  using smena = smenaOch("Kassa");
  throw new Error("Hisobda xato");
} catch (xato) {
  console.log(xato.name);
  console.log("error:", xato.error.message);
  console.log("suppressed:", xato.suppressed.message);
}

Konsolda:

text
SuppressedError
error: Kassa: kassa qulfi ochilmadi
suppressed: Hisobda xato

SuppressedError ("bosilgan xato") — ikki xatoni o'rab olgan yangi tur:

  • error — yangi xato, yopish paytida chiqqani;
  • suppressed — eski xato, blokdagi asl muammo, "bosib qo'yilgan".

Hech biri yo'qolmadi. Xato strategiyasi darsidagi "xatoni yutib yubormang" qoidasini til o'zi bajaryapti. Lekin eng yaxshisi — [Symbol.dispose] ichida xato tashlamaslik: yopish imkon qadar xavfsiz bo'lsin.

8. await using: asinxron yopish

Ba'zi resurslarni yopish vaqt oladi: faylga oxirgi ma'lumotni yozish, serverdagi ulanishni yopish. Buning uchun juft vositalar bor: Symbol.asyncDispose va await using. Ular asinxron kod bilan ishlaydi — await va Promise ni 11-qismda o'rganamiz, hozir bilish shart emas. Shaklini ko'rib qo'ying:

js
await using fayl = await fs.promises.open("chek.txt", "w");
await fayl.write("Osh — 35 000 so'm");
// blok tugaganda fayl o'zi yopiladi

Node 24 da fayl ochganda qaytadigan obyektda [Symbol.asyncDispose] bor, shuning uchun u to'g'ridan-to'g'ri await using bilan ishlaydi. Asinxron resurslar uchun stek ham bor — AsyncDisposableStack.

9. Bugun qayerda ishlaydi — halol ro'yxat

using — eng yangi imkoniyatlardan biri. Uni ishlatishdan oldin muhitni biling.

Standart. JavaScript standartini TC39 qo'mitasi yozadi (JavaScript nima darsida tanishgansiz). Yangi imkoniyat u yerda taklif sifatida 0 dan 4 gacha bosqichdan o'tadi. using 4-bosqichga yetgan — ya'ni standartga qabul qilingan. TC39'ning yakunlangan takliflar ro'yxati bo'yicha u ES2027 nashriga kiradi.

Biz o'zimiz sinab ko'rdik (2026-oktabr):

Muhit Natija
Node 24.21 Ishlaydi, bayroqsiz: using, DisposableStack, SuppressedError
Node 22.17 SyntaxError: Unexpected identifier — ishlamaydi
Chrome 154 Ishlaydi: blokda va modulda; oddiy skriptning yuqori darajasida — yo'q

Node 22 da V8 bayrog'i (--js-explicit-resource-management) bilan sintaksis o'qiladi, lekin bizning sinovda yopish metodi chaqirilmadi va DisposableStack yo'q edi. Ya'ni bayroq bilan ham ishonib bo'lmaydi — Node 24 kerak.

Brauzerlar (web-features 3.40, explicit-resource-management): Chrome va Edge 134 dan, Firefox 141 dan. Safari'da — yo'q. Shuning uchun Baseline holati: "Baseline emas". Telefonlarning katta qismi iPhone bo'lgan auditoriyada bu jiddiy: using li skript Safari'da SyntaxError beradi va butun fayl ishlamaydi (Stack trace darsidagi SyntaxError qoidasi).

Amaliy xulosa:

  • Node kodi (server, skriptlar, testlar) — Node 24 va undan yangisida bemalol ishlating.
  • Brauzer kodi — hozircha try...finally da qoling. Yoki kodni eski sintaksisga aylantiradigan vosita orqali o'tkazing: TypeScript 5.2 dan beri using ni tushunadi va eski brauzerlar uchun try...finally ga aylantira oladi (TypeScript'ni 15-qismda o'rganamiz).
  • Safari qo'llab-quvvatlashni qo'shgach, holat o'zgaradi — web-features va MDN'dagi jadvalni tekshirib turing.

Shu sababli bu darsda vazifalar (brauzer ilovasi) ga using qo'shmaymiz — «Vazifalar qadami» mashqlar repoda, Node'da.

10. Ko'p uchraydigan xatolar

10.1 Oddiy const bilan "yopiladigan" obyekt

const smena = smenaOch() — [Symbol.dispose] bor, lekin hech kim chaqirmaydi. using ga faqat using kalit so'zi ma'no beradi. Tuzatish: using smena = ....

10.2 Metod nomi dispose, simvol emas

{ dispose() { ... } } — oddiy dispose nomli metod. using uni ko'rmaydi: TypeError: Symbol(Symbol.dispose) is not a function. Tuzatish: [Symbol.dispose]() { ... } — kvadrat qavsda simvol.

10.3 Resursni blokdan tashqariga uzatish

js
function papkaOl() {
  using papka = fs.mkdtempDisposableSync("bahor-");
  return papka; // ❌ qaytishdan oldin o'chiriladi
}

Chaqiruvchi allaqachon o'chirilgan papkani oladi. Tuzatish: resursni chaqiruvchining o'zi using bilan oladi, yoki DisposableStack va move().

10.4 Brauzer kodida tekshirmasdan ishlatish

Chrome'da ishladi — demak hamma joyda ishlaydi, deb o'ylash. Safari'da butun fayl SyntaxError bilan to'xtaydi. Tuzatish: brauzer kodida yangi sintaksisdan oldin Baseline holatini tekshiring.

10.5 [Symbol.dispose] ichida xato tashlash

Yopishdagi xato asl xatoni SuppressedError ichiga o'raydi — xabar murakkablashadi. Tuzatish: yopishni xavfsiz yozing; muammo bo'lsa, console.warn bilan bildiring.

11. Mashqlar

1-mashq (oson): Stol band

stolniBandQil(raqam) funksiyasini yozing: u "N-stol band" ni chiqarsin va yopiladigan obyekt qaytarsin, yopilganda "N-stol bo'shadi" chiqsin. Blok ichida 4-stolni band qiling va "Mehmonlar ovqatlanyapti" ni chiqaring.

Yechim
js
function stolniBandQil(raqam) {
  console.log(`${raqam}-stol band`);
  return {
    [Symbol.dispose]() {
      console.log(`${raqam}-stol bo'shadi`);
    },
  };
}

{
  using stol = stolniBandQil(4);
  console.log("Mehmonlar ovqatlanyapti");
}

Konsolda:

text
4-stol band
Mehmonlar ovqatlanyapti
4-stol bo'shadi

stol o'zgaruvchisi blok ichida ishlatilmadi — bu normal. using ning vazifasi — resursni blokka bog'lash, o'zgaruvchi esa faqat "tutqich".

2-mashq (o'rta): Ikki stol va xato

Ikki stolni band qiling (4 va 7), so'ng blok ichida xato tashlang: throw new Error("Oshxona yopiq"). Blokni try...catch ga o'rang. Konsolda nima va qaysi tartibda chiqadi? Avval o'ylab ko'ring, keyin ishga tushiring.

Yechim
js
function stolniBandQil(raqam) {
  console.log(`${raqam}-stol band`);
  return {
    [Symbol.dispose]() {
      console.log(`${raqam}-stol bo'shadi`);
    },
  };
}

try {
  using birinchi = stolniBandQil(4);
  using ikkinchi = stolniBandQil(7);
  throw new Error("Oshxona yopiq");
} catch (xato) {
  console.log("Xato:", xato.message);
}

Konsolda:

text
4-stol band
7-stol band
7-stol bo'shadi
4-stol bo'shadi
Xato: Oshxona yopiq

Ikki qoida birga: teskari tartib (7, keyin 4) va "yopish — xato catch ga yetishidan oldin". try bloki ham blok — using uning oxirida ishlaydi. Yopishda xato chiqmadi — shuning uchun catch asl xatoni oldi, SuppressedError emas.

3-mashq (qiyin): DisposableStack bilan tozalash ro'yxati

Kun oxiridagi tozalash: uch ish teskari tartibda bajarilsin — "Chiroqlar o'chdi", "Kassa yopildi", "Eshik qulflandi" (eshik — eng oxirida). DisposableStack va defer bilan yozing. Bundan tashqari adopt bilan kunlik tushumni (1250000) "seyfga qo'yildi: 1250000" deb yozadigan ishni qo'shing — u kassa yopilishidan oldin bajarilsin. Qaysi tartibda qo'shish kerak?

Yechim
js
{
  using kunOxiri = new DisposableStack();
  kunOxiri.defer(() => console.log("Eshik qulflandi"));
  kunOxiri.defer(() => console.log("Kassa yopildi"));
  kunOxiri.adopt(1250000, (summa) => {
    console.log(`Seyfga qo'yildi: ${summa}`);
  });
  kunOxiri.defer(() => console.log("Chiroqlar o'chdi"));
  console.log("Oxirgi mehmon chiqdi");
}

Konsolda:

text
Oxirgi mehmon chiqdi
Chiroqlar o'chdi
Seyfga qo'yildi: 1250000
Kassa yopildi
Eshik qulflandi

Stek teskari bajariladi, shuning uchun eng oxirida bajarilishi kerak bo'lgan ish (eshik) birinchi qo'shiladi. Qoida: ishlarni "ochilish" tartibida qo'shing — eshik ochildi, kassa ochildi, pul tushdi, chiroq yondi — yopilish o'zi teskari bo'ladi.

4-mashq: Vazifalar qadami — vaqtinchalik jurnal (mashqlar repo)

vazifalar — brauzer ilovasi, using esa Safari'da yo'q (bugungi «Bugun qayerda ishlaydi» bo'limi). Shuning uchun bu qadam kurs/mashqlar/10/59-using/jurnal.mjs da, Node 24 bilan.

JS'da dizayn naqshlari darsida VazifalarRoyxati ga obuna qo'shgan edik: u bekor qilish funksiyasini qaytaradi. Topshiriq:

  1. Kichik royxat yarating: obuna(tinglovchi) (bekor qilish funksiyasini qaytaradi) va qosh(matn) (hamma tinglovchiga matn ni xabar qiladi). VazifalarRoyxati ni to'liq ko'chirish shart emas.
  2. jurnalUla(royxat) funksiyasini yozing: u royxat ga "Jurnal: …" deb yozadigan tinglovchini obuna qilsin va yopiladigan obyekt qaytarsin — yopilganda obuna bekor bo'lsin va "Jurnal yopildi" chiqsin.
  3. Blok ichida using bilan jurnalni ulab, ikki vazifa qo'shing. Blokdan keyin yana bitta qo'shing — u jurnalga tushmasligi kerak.
Yechim

kurs/mashqlar/10/59-using/jurnal.mjs:

js
function royxatYarat() {
  let tinglovchilar = [];
  return {
    obuna(tinglovchi) {
      tinglovchilar.push(tinglovchi);
      return () => {
        tinglovchilar = tinglovchilar.filter((t) => t !== tinglovchi);
      };
    },
    qosh(matn) {
      tinglovchilar.forEach((t) => t(matn));
    },
  };
}

function jurnalUla(royxat) {
  const bekorQil = royxat.obuna((matn) => {
    console.log(`Jurnal: «${matn}» qo'shildi`);
  });
  return {
    [Symbol.dispose]() {
      bekorQil();
      console.log("Jurnal yopildi");
    },
  };
}

const royxat = royxatYarat();
{
  using jurnal = jurnalUla(royxat);
  royxat.qosh("Non olish");
  royxat.qosh("Choy damlash");
}
royxat.qosh("Kitob o'qish");
console.log("Uchinchi vazifa jurnalsiz qo'shildi");

Konsolda:

text
Jurnal: «Non olish» qo'shildi
Jurnal: «Choy damlash» qo'shildi
Jurnal yopildi
Uchinchi vazifa jurnalsiz qo'shildi

node jurnal.mjs bilan ishga tushiring (Node 24). Nimaga e'tibor bering:

  • "Obuna — bekor qilish" jufti endi bitta obyekt. jurnalUla ichida bekor qilish funksiyasi closure'da saqlanadi (Closure amaliyotda), [Symbol.dispose] uni chaqiradi. Tashqi kod bekor qilishni umuman ko'rmaydi — faqat blokni.
  • DisposableStack varianti. jurnalUla siz ham bo'ladi: using stek = new DisposableStack(); stek.defer(royxat.obuna(...)); — «Vaqtinchalik obuna» bo'limidagidek.
  • Brauzerdagi ekvivalent. vazifalar.js da xuddi shu ishni try...finally bilan yozish mumkin: const bekorQil = holat.vazifalar.obuna(...) va finally { bekorQil(); }. using u yerga Safari qo'llab-quvvatlashi paydo bo'lgach keladi.

Node 22 da ishga tushirib ko'ring: SyntaxError: Unexpected identifier 'jurnal' — versiya muhimligining amaliy isboti.

Commit (mashqlar repo): 10/59: using bilan vaqtinchalik jurnal.

12. Real ishda

  • Server kodi. Ma'lumotlar bazasi ulanishi, fayl, qulf (lock) — "och va albatta yop" juftlari backend'da har qadamda. Node kutubxonalari [Symbol.dispose] va [Symbol.asyncDispose] ni qo'shib bormoqda; using bilan ulanishni qaytarishni unutish imkonsiz bo'ladi.
  • Testlar. Test uchun vaqtinchalik papka, soxta server yoki taymer — test tugashi bilan tozalanishi shart. fs.mkdtempDisposableSync kabi API'lar aynan shu uchun qo'shilgan. Testlarni 12-qismda yozamiz.
  • Boshqa tillar. G'oya yangi emas: C# da using, Python'da with, Java'da try-with-resources. Bir tilda o'rgansangiz, boshqasida ham taniysiz — Python kursiga o'tsangiz, with open(...) aynan shu.
  • Intervyu. Hozircha kam so'raladi, lekin "resurs oqishi nima va uni qanday oldini olasiz?" savolida try...finally va using ni tilga olish — kuchli javob.

Xulosa

  • Resurs — ochilgan va albatta yopilishi kerak bo'lgan narsa: fayl, taymer, obuna, vaqtinchalik papka.
  • using x = resurs — blok tugaganda x[Symbol.dispose]() chaqiriladi: xato va return da ham. Mohiyatan — try...finally.
  • Bir nechta using — teskari tartibda yopiladi; qiymat — yopiladigan obyekt, null yoki undefined; qayta tayinlab bo'lmaydi.
  • DisposableStack — oddiy funksiyalarni (defer), obyektlarni (use) va qiymatlarni (adopt) yopishga qo'shadi.
  • Blok va yopish ikkalasi xato bersa — SuppressedError (error + suppressed); await using — asinxron yopish.
  • TC39 4-bosqich (ES2027), Node 24 va Chrome/Firefox'da bor, Safari'da yo'q — Baseline emas: Node'da ishlating, brauzerda hozircha try...finally.

Keyingi dars: Nega modullar va ESM export — vazifalar.js ni fayllarga bo'lishni boshlaymiz: nega bitta katta fayl muammo va export nima beradi.

Manbalar

  • MDN: "using", "Symbol.dispose", "DisposableStack", "SuppressedError", "JavaScript resource management" — developer.mozilla.org
  • TC39: "Explicit Resource Management" taklifi — github.com/tc39/proposal-explicit-resource-management; Finished Proposals (ES2027)
  • web-features 3.40 (npm paketi): explicit-resource-management — Baseline emas (Chrome 134, Firefox 141, Safari yo'q)
  • Node.js 24: "fs.mkdtempDisposableSync()", "Timers — timeoutSymbol.dispose" — nodejs.org/api
Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
JavaScript using: resurslarni aniq boshqarish va Symbol.dispose — IlmHamroh