Mundarija (29)
- Bu darsda
- 1. Nega bu kerak?
- 2. Muammo: global o'zgaruvchi
- 3. AsyncLocalStorage: run va getStore
- 3.1 Asosiy misol
- 3.2 Qaysi asinxron ishlar kontekstni saqlaydi
- 3.3 Ichma-ich run
- 4. Kontekst yo'qoladigan joylar
- 4.1 EventEmitter: tinglovchi kimning kontekstida?
- 4.2 snapshot va exit
- 5. async_hooks va ALS narxi
- 5.1 Eski yo'l: createHook
- 5.2 Narxi qancha?
- 5.3 Kelajak: AsyncContext
- 6. Kanon qarori: valyuta da ALS yo'q
- 7. Ko'p uchraydigan xatolar
- 7.1 Global o'zgaruvchida so'rov holati
- 7.2 run dan tashqarida getStore()
- 7.3 Hodisa tinglovchisidan ro'yxatdagi kontekstni kutish
- 7.4 ALS'ni "yashirin argument" sifatida suiiste'mol qilish
- 7.5 Har funksiyada yangi AsyncLocalStorage
- 8. Mashqlar
- 1-mashq (oson): Natijani ayting
- 2-mashq (o'rta): Avtomatik so'rov ID'si va vaqt
- 3-mashq (qiyin): Kontekstni yo'qotadigan navbat
- 4-mashq: CLI qadami — tajriba, loyihaga kirmaydi
- 9. Real ishda
- Xulosa
- Manbalar
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 birawait,setTimeoutvathenkeyinals.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 000await— 7.2 ms va 6.3 ms.
Bu darsda
- Global o'zgaruvchi parallel async ishda nega buzilishini ko'rsata olasiz.
runvagetStorebilan so'rov ID'sini log'larga argumentsiz qo'shasiz.exit,snapshotvaAsyncResource.bindni qachon ishlatishni bilasiz.- EventEmitter va kontekst yo'qolishi tuzog'ini tushuntira olasiz.
async_hooksning 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
asyncfunksiya. 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?
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:
[A] USD so'raldi
[B] EUR so'raldi
[B] EUR keldi
[B] USD keldiOxirgi 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:
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:
[A] USD so'raldi
[B] EUR so'raldi
[B] EUR keldi
[A] USD keldi
[-] hammasi tugadiEndi 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 tiklandi3.2 Qaysi asinxron ishlar kontekstni saqlaydi
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:
[-] run'dan tashqarida
[T] then
[T] microtask
[T] setTimeoutHammasi — [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:
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:
[ichki] ichkarida
[tashqi] tashqariga qaytdiBu so'rov ichidagi kichik bosqich uchun foydali: masalan, butun so'rovning ID'si va har qayta urinishning raqami.
Tekshirib ko'ring:
runga 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:
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:
[B] tinglovchi
[-] tinglovchi
[A] bind bilanTinglovchi 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":
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:
[S] snapshot ichida
[-] exit ichidasurat(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:
[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- 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.
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:
2: hech kim
1: Ali
3: AliYechim
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
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:
[#1] boshlandi
[#2] boshlandi
[#2] tarix tugadi
[#1] kurs tugadifinally — 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.
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
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:
[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):
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:
awaitdan 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 —
emitqilganning 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
- Node.js hujjati: Asynchronous context tracking — AsyncLocalStorage (v24).
- Node.js hujjati: Async hooks (Experimental ogohlantirishi bilan).
- TC39: AsyncContext taklifi.
Izohlar (0)
Izoh yozish uchun kiring.
- Hozircha izoh yo'q. Birinchi bo'ling!