IlmHamroh
JavaScript Full-stack/24-qism. Node.js39/55-dars16 daqiqa
Mundarija (29)

Node.js AsyncLocalStorage: so'rov kontekstini async zanjir bo'ylab olib yurish

Qisqacha: AsyncLocalStorage (ALS) — "shu async zanjirga tegishli" qiymatni saqlaydigan joy. als.run(qiymat, funksiya) ichida boshlangan har bir await, setTimeout va then keyin als.getStore() bilan o'sha qiymatni oladi — uni har funksiyaga argument qilib uzatish shart emas. Ikki parallel "so'rov" bir-birining ID'sini aralashtirmaydi. Kanon o'lchovida narxi shovqin darajasida chiqdi: 100 000 await — 7.2 ms va 6.3 ms.

Bu darsda

  • Global o'zgaruvchi parallel async ishda nega buzilishini ko'rsata olasiz.
  • run va getStore bilan so'rov ID'sini log'larga argumentsiz qo'shasiz.
  • exit, snapshot va AsyncResource.bind ni qachon ishlatishni bilasiz.
  • EventEmitter va kontekst yo'qolishi tuzog'ini tushuntira olasiz.
  • async_hooks ning eskirgan qismi va ALS narxi haqida o'lchov bilan gapirasiz.

Oldin bilishingiz kerak: libuv thread pool, Microtask navbati, process.nextTick va setImmediate, EventEmitter chuqur, timers va timers/promises.

1. Nega bu kerak?

Server bir vaqtda yuzlab so'rovni bajaradi. Bittasi xato berdi — log'da shunday qator paydo bo'ldi: CBU javob bermadi. Qaysi foydalanuvchining so'rovi? Qaysi so'rov qayta urinishdan keyin tugadi? Log qatorlari aralashib ketgan, ularni bir-biriga bog'lab bo'lmaydi.

Odatiy yechim — har so'rovga ID (identifikator) berish va har log qatoriga qo'shish: [A7f3] CBU javob bermadi. Muammo shundaki, log yozadigan funksiya chuqurda turadi: handler → servis → API klient → qayta urinish → log. ID'ni har funksiyaga argument qilib uzatish kerak bo'ladi. Bu "argument quvurlari": o'n funksiyaning imzosi faqat bitta qiymatni pastga uzatish uchun o'zgaradi.

AsyncLocalStorage bu muammoni hal qiladi. Qiymat zanjir boshida bir marta qo'yiladi, zanjirning istalgan joyida o'qiladi.

Hayotiy o'xshatish: kasalxonada bemorning qo'liga bilaguzuk taqiladi — unda ism va karta raqami. Bemor bo'limdan bo'limga o'tadi: rentgen, laboratoriya, shifokor. Hech kim uning qog'ozlarini qo'lma-qo'l uzatmaydi — har xodim bilaguzukka qaraydi. ALS — o'sha bilaguzuk, faqat u asinxron "safar"ga taqiladi: qayerga await bilan borsa ham, bilaguzuk u bilan birga.

Diqqat: ALS'ning eng ko'p ishlatiladigan joyi — HTTP server (har so'rovga kontekst). HTTP'ni 25-qismda o'rganamiz. Bu darsda "so'rov" — bizning taqlidimiz: bir nechta parallel async funksiya. Mexanizm aynan bir xil.

2. Muammo: global o'zgaruvchi

Eng sodda g'oya — joriy so'rov ID'sini global o'zgaruvchida saqlash. Bir vaqtda bitta so'rov bo'lsa, bu ishlaydi. Ikkita parallel bo'lsa-chi?

js
import { setTimeout as kut } from "node:timers/promises";

let joriyId = "-"; // ❌ hamma so'rovlar uchun bitta

function log(xabar) {
  console.log(`[${joriyId}] ${xabar}`);
}

async function sorovniBajar(id, kod, ms) {
  joriyId = id;
  log(`${kod} so'raldi`);
  await kut(ms); // tarmoq o'rnida
  log(`${kod} keldi`);
}

await Promise.all([
  sorovniBajar("A", "USD", 20),
  sorovniBajar("B", "EUR", 10),
]);

Konsolda:

text
[A] USD so'raldi
[B] EUR so'raldi
[B] EUR keldi
[B] USD keldi

Oxirgi qatorga qarang: USD keldi — bu A so'rovi, lekin log [B] deydi. A await da kutib turganda B boshlandi va global o'zgaruvchini o'zgartirdi. A uyg'onganda joriyId allaqachon "B". Log yolg'on gapirdi — bu xatoni ishlab chiqarishda topish juda qiyin.

Vaqt bo'yicha qadamma-qadam:

Qadam Nima bo'ldi joriyId Konsolda
1 A boshlandi "A" [A] USD so'raldi
2 A await kut(20) da to'xtadi "A" —
3 B boshlandi "B" [B] EUR so'raldi
4 B await kut(10) da to'xtadi "B" —
5 10 ms: B uyg'ondi "B" [B] EUR keldi
6 20 ms: A uyg'ondi "B" — xato! [B] USD keldi

Muammo 6-qadamda: A uyg'ondi, lekin global o'zgaruvchida hali ham B'ning ID'si turibdi.

Muammoning ildizi event loop da: await funksiyani to'xtatadi va boshqa kodga navbat beradi. Global o'zgaruvchi esa "kim navbatda" ekanini bilmaydi.

3. AsyncLocalStorage: run va getStore

3.1 Asosiy misol

Endi xuddi shu kodni ALS bilan yozamiz. Farq ikki joyda: run kontekstni boshlaydi, getStore uni o'qiydi. kursniOl ning imzosida ID yo'q:

js
import { AsyncLocalStorage } from "node:async_hooks";
import { setTimeout as kut } from "node:timers/promises";

const kontekst = new AsyncLocalStorage();

function log(xabar) {
  const sorov = kontekst.getStore();
  console.log(`[${sorov?.id ?? "-"}] ${xabar}`);
}

async function kursniOl(kod, ms) {
  log(`${kod} so'raldi`);
  await kut(ms); // tarmoq o'rnida
  log(`${kod} keldi`);
}

function sorovniBajar(id, kod, ms) {
  return kontekst.run({ id }, () => kursniOl(kod, ms));
}

await Promise.all([
  sorovniBajar("A", "USD", 20),
  sorovniBajar("B", "EUR", 10),
]);
log("hammasi tugadi");

Konsolda:

text
[A] USD so'raldi
[B] EUR so'raldi
[B] EUR keldi
[A] USD keldi
[-] hammasi tugadi

Endi USD keldi to'g'ri — [A]. Har so'rov o'z kontekstini "olib yurdi". run dan tashqarida getStore() — undefined, shuning uchun oxirgi qatorda -. Bu kanon tajribasi bilan bir xil natija (t39-als.txt: [so'rov B], [so'rov A], tashqarida -).

Qanday ishlaydi? run boshlanganda Node joriy "async kontekst" ga qiymatni bog'laydi. Shu kontekstda yaratilgan har bir asinxron ish — Promise, taymer, I/O callback — o'zi yaratilgan paytdagi kontekstni eslab qoladi. Callback keyinroq bajarilganda, Node o'sha kontekstni tiklaydi.

sequenceDiagram
  participant L as Event loop
  participant A as run({id: A})
  participant B as run({id: B})
  A->>L: await kut(20) — kontekst A saqlandi
  B->>L: await kut(10) — kontekst B saqlandi
  L-->>B: 10 ms: kontekst B tiklandi
  L-->>A: 20 ms: kontekst A tiklandi

3.2 Qaysi asinxron ishlar kontekstni saqlaydi

js
import { AsyncLocalStorage } from "node:async_hooks";

const als = new AsyncLocalStorage();
const log = (m) => console.log(`[${als.getStore()?.id ?? "-"}] ${m}`);

als.run({ id: "T" }, () => {
  setTimeout(() => log("setTimeout"), 5);
  Promise.resolve().then(() => log("then"));
  queueMicrotask(() => log("microtask"));
});
log("run'dan tashqarida");

Konsolda:

text
[-] run'dan tashqarida
[T] then
[T] microtask
[T] setTimeout

Hammasi — [T]. Tartib esa microtask navbati darsidagidek: avval sinxron kod, keyin microtask'lar, keyin taymer. run sinxron qaytdi, lekin u ichida boshlangan ishlar kontekstni o'zi bilan olib ketdi.

3.3 Ichma-ich run

run ichida yana run chaqirish mumkin — ichkarida yangi qiymat, chiqqanda eskisi tiklanadi:

js
import { AsyncLocalStorage } from "node:async_hooks";

const als = new AsyncLocalStorage();
const log = (m) => console.log(`[${als.getStore()?.id ?? "-"}] ${m}`);

als.run({ id: "tashqi" }, () => {
  als.run({ id: "ichki" }, () => log("ichkarida"));
  log("tashqariga qaytdi");
});

Konsolda:

text
[ichki] ichkarida
[tashqi] tashqariga qaytdi

Bu so'rov ichidagi kichik bosqich uchun foydali: masalan, butun so'rovning ID'si va har qayta urinishning raqami.

Tekshirib ko'ring: run ga obyekt beryapmiz: { id: "A" }. Uning ichidagi maydonni keyin o'zgartirsak (als.getStore().foydalanuvchi = "Ali"), bu boshqa so'rovlarga ta'sir qiladimi?

Javob

Faqat shu so'rovga. Har run o'z obyektini oldi, getStore() aynan shu obyektni qaytaradi (nusxa emas). Uni o'zgartirsangiz, shu kontekstdagi keyingi kod yangi qiymatni ko'radi. Boshqa so'rovning obyekti boshqa. Ehtiyot bo'lish kerak bo'lgan joy: bitta obyektni ikki run ga bersangiz — ikkalasi umumiy bo'lib qoladi.

4. Kontekst yo'qoladigan joylar

4.1 EventEmitter: tinglovchi kimning kontekstida?

ALS asinxron ishlarni kuzatadi. EventEmitter esa asinxron emas — emit tinglovchilarni sinxron, shu zahotiyoq chaqiradi (EventEmitter chuqur). Demak tinglovchi o'zi ro'yxatdan o'tgan kontekstda emas, emit chaqirilgan kontekstda ishlaydi:

js
import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";
import { EventEmitter } from "node:events";

const als = new AsyncLocalStorage();
const log = (m) => console.log(`[${als.getStore()?.id ?? "-"}] ${m}`);

const e = new EventEmitter();
als.run({ id: "A" }, () => e.on("tayyor", () => log("tinglovchi")));
als.run({ id: "B" }, () => e.emit("tayyor"));
e.emit("tayyor");

const e2 = new EventEmitter();
als.run({ id: "A" }, () => {
  e2.on("tayyor", AsyncResource.bind(() => log("bind bilan")));
});
als.run({ id: "B" }, () => e2.emit("tayyor"));

Konsolda:

text
[B] tinglovchi
[-] tinglovchi
[A] bind bilan

Tinglovchi A kontekstida ro'yxatdan o'tdi, lekin [B] va [-] ni ko'rdi — kim emit qilsa, o'shaniki. Bu ko'pincha to'g'ri xulq (hodisa kimning ishi natijasida chiqdi — o'sha). Lekin ba'zan kerakli kontekst — ro'yxatdan o'tgan payt. Masalan, ulanishlar hovuzi (pool) bir mijozning callback'ini boshqa mijozning ishi paytida chaqiradi.

AsyncResource.bind(funksiya) — funksiyani hozirgi kontekstga "mixlaydi". Keyin u kim chaqirsa ham, o'sha kontekstda ishlaydi: [A] bind bilan.

4.2 snapshot va exit

Bir nechta ALS bo'lishi mumkin (log uchun, foydalanuvchi uchun, tranzaksiya uchun). AsyncLocalStorage.snapshot() — hamma ALS'larning joriy holatini bitta funksiyaga "suratga oladi":

js
import { AsyncLocalStorage } from "node:async_hooks";

const als = new AsyncLocalStorage();
const log = (m) => console.log(`[${als.getStore()?.id ?? "-"}] ${m}`);

const surat = als.run({ id: "S" }, () => {
  return AsyncLocalStorage.snapshot();
});
surat(() => log("snapshot ichida"));
als.run({ id: "X" }, () => als.exit(() => log("exit ichida")));

Konsolda:

text
[S] snapshot ichida
[-] exit ichida

surat(fn) — fn ni surat olingan paytdagi kontekstda bajaradi. als.exit(fn) esa teskarisi: fn ni kontekstsiz bajaradi. Masalan, fon vazifasini boshlayotganda — u so'rov tugagandan keyin ham ishlaydi va so'rovning ID'sini "o'g'irlab" log'ni chalkashtirmasligi kerak.

Metod Nima qiladi
als.run(qiymat, fn) fn va undan boshlangan async ish — shu qiymat bilan
als.getStore() joriy qiymat yoki undefined
als.exit(fn) fn — kontekstsiz
AsyncLocalStorage.snapshot() joriy holatni keyin qayta ishlatish uchun funksiya
AsyncResource.bind(fn) fn ni joriy kontekstga mixlaydi
als.enterWith(qiymat) joriy sinxron bajarilishga qiymat qo'yadi — ehtiyot bilan

enterWith — run siz kontekst qo'yish. U joriy funksiya va undan keyingi hamma kodga ta'sir qiladi, chegarasi aniq emas. Hujjat ham run ni afzal ko'radi. Uni faqat kutubxona chuqurligida, run bilan o'rab bo'lmaganda ishlatishadi.

Tekshirib ko'ring: Callback qabul qiladigan eski kutubxona (masalan, o'z navbatiga callback yig'ib, keyin bittada chaqiradigan) bilan ishlayapsiz va log'larda ID - chiqyapti. Nima qilasiz?

Javob

Kutubxona callback'ni boshqa kontekstda chaqiryapti (navbat to'plangan paytda emas). Callback'ni kutubxonaga berishdan oldin AsyncResource.bind(callback) bilan o'rang — u yaratilgan paytdagi kontekstda ishlaydi. Zamonaviy kutubxonalar buni o'zi qiladi; eski kodda esa shu bitta o'ram yordam beradi.

5. async_hooks va ALS narxi

5.1 Eski yo'l: createHook

ALS node:async_hooks modulida turadi. Shu modulda eski, past darajali API ham bor — createHook. U har asinxron resurs yaratilganda va bajarilganda chaqiriladigan "ilgaklar" (hook) beradi: init, before, after, destroy. Ilgari kontekstni shu bilan qo'lda yasashardi.

Node hujjati createHook, executionAsyncResource kabi API'larni Experimental deb belgilaydi va ulardan ko'chishni tavsiya qiladi: ular sekin, xatoga moyil va ichki tafsilotlarga bog'liq. AsyncLocalStorage va AsyncResource esa — barqaror (Stable). Eski kodda createHook ni ko'rsangiz, bu "ALS'ga ko'chirish kerak" degan belgi.

5.2 Narxi qancha?

ALS'ning narxi uzoq vaqt bahs mavzusi bo'lgan: eski implementatsiya har Promise'ga ilgak qo'yardi. Node 24.0.0 reliz eslatmasida yozilgan: ALS endi sukut bo'yicha AsyncContextFrame asosida ishlaydi (PR #55552). AsyncContextFrame — kontekstni har Promise'ga ilgak qo'ymasdan, V8'ning o'zida saqlaydigan yangi mexanizm. Reliz eslatmasiga ko'ra u samaraliroq.

Kanon o'lchadi (t39-als.txt, Node 24.21.0, Windows 11, i5-12500H). Oxirgi qatorga qarang — ikki son deyarli teng:

text
[so'rov B] 10 ms ish tugadi
[so'rov A] 20 ms ish tugadi
[so'rov -] tashqarida
1e5 await: ALS'siz 7.2 ms, ALS ichida 6.3 ms
100 000 ta await: ALS narxi
  • ALS'siz7,2 ms
  • ALS run ichidashovqin darajasida6,3 ms

Manba: IlmHamroh kanon o'lchovi, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H (t39-als.txt)

ALS ichida hatto tezroq chiqdi (6.3 ms) — bu albatta ALS tezlashtirgani emas. Farq o'lchov shovqini darajasida: bitta o'lchov, isitishsiz. Halol xulosa: Node 24 da bu sinovda ALS narxi ko'rinmadi. Eski Node versiyalarida va murakkab ilovalarda narx ko'proq bo'lishi mumkin — kanon ularni o'lchamadi.

Bu o'lchov sizning kompyuteringizda boshqacha chiqadi. Uni tekshiruvchi qayta yurgizmaydi — natija vaqtga bog'liq, shuning uchun kanon xom faylidan keltirildi.

5.3 Kelajak: AsyncContext

JavaScript standartiga xuddi shu g'oyani kiritish taklifi bor — TC39 AsyncContext (AsyncContext.Variable). U qabul qilinsa, ALS'ning egizagi brauzerda ham ishlaydi. 2026-10 holatiga u hali standartga kirmagan taklif — ishlab chiqarishda Node'ning ALS'i ishlatiladi.

6. Kanon qarori: valyuta da ALS yo'q

Kanon ALS'ni o'lchadi va loyihaga qo'shmadi. valyuta CLI'sida bir vaqtda bitta buyruq bajariladi. Log va xato xabarlari aniq bitta "so'rov" ga tegishli — aralashish yo'q. Kontekstni ajratadigan narsa ham yo'q.

valyuta server bo'lganda (masalan, kurslarni HTTP orqali beradigan API — 25-qism) ALS darhol kerak bo'lardi: har so'rovga ID, log'larda shu ID, xatoda — qaysi so'rov ekanligi. Bu dars uchun "CLI qadami" o'rnida — o'sha holatni taqlid qiladigan 4-mashq.

7. Ko'p uchraydigan xatolar

7.1 Global o'zgaruvchida so'rov holati

«Muammo: global o'zgaruvchi» bo'limidagi xato: await dan keyin qiymat boshqa so'rovniki. ALS yoki aniq argument.

7.2 run dan tashqarida getStore()

getStore() — undefined. getStore().id esa TypeError: Cannot read properties of undefined (reading 'id') bilan yiqiladi. als.getStore()?.id ?? "-" — ixtiyoriy zanjir va sukut qiymati.

7.3 Hodisa tinglovchisidan ro'yxatdagi kontekstni kutish

Tinglovchi emit qilgan kontekstda ishlaydi. Kerak bo'lsa — AsyncResource.bind.

7.4 ALS'ni "yashirin argument" sifatida suiiste'mol qilish

Biznes ma'lumotlarini (summa, valyuta kodi) ALS orqali uzatish xavfli. Funksiya imzosi yolg'on gapiradi: u nimaga bog'liqligi ko'rinmaydi, test yozish qiyinlashadi. ALS — faqat kesib o'tuvchi narsalar uchun: so'rov ID'si, joriy foydalanuvchi, til, tranzaksiya, trace. Asosiy ma'lumot — argumentda.

7.5 Har funksiyada yangi AsyncLocalStorage

new AsyncLocalStorage() ni funksiya ichida yaratsangiz, har chaqiruvda yangi ombor — getStore() doim bo'sh. Bitta nusxa, modul darajasida, eksport qilinadi.

8. Mashqlar

1-mashq (oson): Natijani ayting

Quyidagi kod nima chiqaradi? Avval o'ylang, keyin o'z kompyuteringizda tekshiring.

js
import { AsyncLocalStorage } from "node:async_hooks";

const als = new AsyncLocalStorage();
const kim = () => als.getStore() ?? "hech kim";

als.run("Ali", async () => {
  await null;
  console.log("1:", kim());
  setTimeout(() => console.log("3:", kim()), 0);
});
console.log("2:", kim());

Konsolda:

text
2: hech kim
1: Ali
3: Ali
Yechim

run ichidagi async funksiya await null da to'xtaydi va boshqaruvni qaytaradi — shuning uchun avval tashqaridagi 2: hech kim chiqadi. Keyin microtask: 1: Ali — kontekst await dan keyin tiklandi. Oxirida taymer: 3: Ali — taymer run kontekstida yaratilgan. ALS'ga qiymat sifatida satr ham berish mumkin, obyekt shart emas.

2-mashq (o'rta): Avtomatik so'rov ID'si va vaqt

sorovBilan(nom, fn) funksiyasini yozing: u har chaqiruvga ketma-ket raqam (1, 2, …) va boshlanish vaqtini ALS'ga qo'ysin va fn ni bajarsin. log(xabar) esa [#1] xabar ko'rinishida yozsin. Tugaganda sorovBilan o'zi [#1] <nom> tugadi deb yozsin. Ikki parallel chaqiruv bilan sinang: "kurs" (20 ms) va "tarix" (10 ms). Eslatma: setTimeout timers/promises dan (timers).

Yechim
js
import { AsyncLocalStorage } from "node:async_hooks";
import { setTimeout as kut } from "node:timers/promises";

const als = new AsyncLocalStorage();
let keyingi = 1;

function log(xabar) {
  const k = als.getStore();
  console.log(`[${k ? "#" + k.raqam : "-"}] ${xabar}`);
}

function sorovBilan(nom, fn) {
  const kontekst = { raqam: keyingi++, boshi: performance.now() };
  return als.run(kontekst, async () => {
    try {
      return await fn();
    } finally {
      log(`${nom} tugadi`);
    }
  });
}

await Promise.all([
  sorovBilan("kurs", async () => {
    log("boshlandi");
    await kut(20);
  }),
  sorovBilan("tarix", async () => {
    log("boshlandi");
    await kut(10);
  }),
]);

Konsolda:

text
[#1] boshlandi
[#2] boshlandi
[#2] tarix tugadi
[#1] kurs tugadi

finally — xato bo'lsa ham "tugadi" yoziladi. Kontekstda boshi ham bor: log'ga ${(performance.now() - k.boshi).toFixed(0)} ms qo'shib, har so'rov qancha davom etganini yozish mumkin. Biz uni chiqishga qo'shmadik — vaqt har ishga tushishda boshqacha bo'ladi.

3-mashq (qiyin): Kontekstni yo'qotadigan navbat

Quyidagi Navbat callback'larni yig'adi va 10 ms dan keyin hammasini bittada chaqiradi (eski kutubxonalar shunday qiladi). Ikki "so'rov" (A, B) navbatga callback qo'shadi. (1) Callback'lar qaysi ID'ni ko'radi? (2) Navbat ni o'zgartirmasdan, faqat chaqiruvchi tomonda tuzating. Eslatma: AsyncResource.bind — «EventEmitter: tinglovchi kimning kontekstida?» bo'limida.

js
class Navbat {
  #callbacklar = [];
  qosh(cb) {
    this.#callbacklar.push(cb);
    if (this.#callbacklar.length === 1) {
      setTimeout(() => {
        for (const c of this.#callbacklar.splice(0)) c();
      }, 10);
    }
  }
}
Yechim
js
import { AsyncLocalStorage, AsyncResource } from "node:async_hooks";

const als = new AsyncLocalStorage();
const log = (m) => console.log(`[${als.getStore() ?? "-"}] ${m}`);

class Navbat {
  #callbacklar = [];
  qosh(cb) {
    this.#callbacklar.push(cb);
    if (this.#callbacklar.length === 1) {
      setTimeout(() => {
        for (const c of this.#callbacklar.splice(0)) c();
      }, 10);
    }
  }
}

const oddiy = new Navbat();
als.run("A", () => oddiy.qosh(() => log("oddiy")));
als.run("B", () => oddiy.qosh(() => log("oddiy")));

const tuzatilgan = new Navbat();
const bogla = (fn) => AsyncResource.bind(fn);
als.run("A", () => tuzatilgan.qosh(bogla(() => log("bind"))));
als.run("B", () => tuzatilgan.qosh(bogla(() => log("bind"))));

Konsolda:

text
[A] oddiy
[A] oddiy
[A] bind
[B] bind

(1) Ikkala callback ham [A] ni ko'rdi. Taymer A'ning kontekstida yaratilgan (birinchi qosh), B'ning callback'i esa o'sha taymer ichida chaqirildi. B'ning log'i A'ning nomidan yozildi — global o'zgaruvchidagi xatoning boshqa ko'rinishi. (2) AsyncResource.bind har callback'ni o'zi yaratilgan kontekstga mixladi — [A] va [B] to'g'ri.

4-mashq: CLI qadami — tajriba, loyihaga kirmaydi

valyuta ga kod qo'shilmaydi. Kanon tajribasini o'zingiz takrorlang: 1e5 marta await Promise.resolve() ni avval oddiy, keyin als.run(...) ichida bajarib, vaqtni performance.now() bilan o'lchang. Isitish qo'shing (avval bir marta yurgizing) va 5 marta takrorlab, medianani oling. Natijangiz kanon bilan (7.2 / 6.3 ms) qanday solishtiriladi? Keyin bir gapda yozing: valyuta server bo'lsa, ALS'ga nimani qo'yardingiz?

Yechim

Kanon skriptining o'lchov qismi (tajriba/t39-als.mjs):

js
let s = performance.now();
for (let i = 0; i < 1e5; i++) await Promise.resolve();
const oddiy = performance.now() - s;
s = performance.now();
await als.run({ id: 1 }, async () => {
  for (let i = 0; i < 1e5; i++) await Promise.resolve();
});
const alsli = performance.now() - s;
console.log(`ALS'siz ${oddiy.toFixed(1)} ms`);
console.log(`ALS ichida ${alsli.toFixed(1)} ms`);

Kanon isitishsiz va bir marta o'lchagan — shuning uchun mashqda isitish va mediana so'raldi. Kutiladigan xulosa: Node 24 da farq bir necha foiz ichida, ko'pincha shovqindan ajratib bo'lmaydi. Gap namunasi: "Har HTTP so'rovga { id, boshi, foydalanuvchi } — log'larda ID, oxirida davomiylik, xatoda kim ekanligi."

9. Real ishda

  • Log va trace: pino, winston kabi loggerlar va OpenTelemetry (taqsimlangan trace — so'rov bir nechta xizmatdan o'tganini kuzatish) ALS ustida ishlaydi. So'rov ID'si log'larda o'z-o'zidan paydo bo'ladi.
  • Framework'lar: Next.js server qismi (23-qismda ishlatgan headers(), cookies()) va NestJS'ning so'rov doirasi ichkarida shunga o'xshash mexanizmga tayanadi — shuning uchun ular argumentsiz "joriy so'rovni" biladi.
  • Bazaga ulanish: tranzaksiyani ALS'da saqlab, ichma-ich funksiyalar bitta tranzaksiyadan foydalanadi (26–27-qismlarda).
  • Intervyu savollari: "Node'da so'rov kontekstini qanday olib yurasiz?", "AsyncLocalStorage qanday ishlaydi?", "Nega global o'zgaruvchi ishlamaydi?".

Xulosa

  • Global o'zgaruvchi parallel async ishda yolg'on beradi: await dan keyin boshqa so'rovning qiymati.
  • als.run(qiymat, fn) — kontekst boshlanadi; als.getStore() — zanjirning istalgan joyida o'qiladi.
  • Promise, taymer, microtask kontekstni saqlaydi; EventEmitter tinglovchisi — emit qilganning kontekstida.
  • AsyncResource.bind, snapshot, exit — kontekstni mixlash, suratga olish, chiqib ketish.
  • createHook — eskirgan yo'l; ALS — barqaror. Kanonda Node 24 da narxi shovqin darajasida (7.2 / 6.3 ms).

Keyingi dars: --watch, --env-file va node --run — Node'ning o'rnatilgan dev vositalari bilan valyuta ni qayta ishga tushirish va muhit o'zgaruvchilarini yuklash.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js AsyncLocalStorage: so'rov kontekstini async zanjir bo'ylab olib yurish — IlmHamroh