IlmHamroh
JavaScript Full-stack/24-qism. Node.js3/55-dars19 daqiqa
Mundarija (39)

Node.js event loop fazalari: timers, poll, check va bitta aylanish

Qisqacha: Node'ning event loop'i bitta task navbati emas — u fazalar doirasi: timers (setTimeout), pending, idle/prepare, poll (fayl va tarmoq callback'lari, kutish), check (setImmediate), close. Doiraning bitta to'liq aylanishi — loop aylanishi (tick). Fazalar tartibi sabab I/O callback ichida setImmediate doim setTimeout(0) dan oldin ishlaydi, faylning yuqori darajasida esa ularning tartibi kafolatlanmagan. Navbatda hech narsa qolmasa, loop to'xtaydi va jarayon tugaydi.

Bu darsda

  • Node event loop'ining oltita fazasini tartibi bilan ayta olasiz va har biriga qaysi callback tushishini bilasiz.
  • Poll fazasi qachon kutishini, qachon kutmasligini tushuntira olasiz.
  • setImmediate va setTimeout(0) tartibini uch joyda — I/O callback'ida, taymer ichida va faylning yuqori darajasida — oldindan ayta olasiz.
  • Node jarayoni nega ba'zan o'zi tugaydi, ba'zan esa "osilib" turishini tushuntira olasiz.
  • Fazalarni o'lchov bilan tekshirishni bilasiz: bir marta ishga tushirish yetmaydi.

Oldin bilishingiz kerak: Event loop (task, microtask, call stack), Microtask va macrotask chuqur, Node.js nima, Blocking va non-blocking I/O.

1. Nega bu kerak?

Event loop darsida brauzer qoidasini o'rgangan edingiz: sinxron kod → hamma microtask'lar → bitta task → yana microtask'lar. O'sha darsning oxirida ikki g'alati holat qolgan edi:

  • Fayl o'qilgandan keyin setImmediate doim setTimeout(fn, 0) dan oldin ishladi.
  • Faylning yuqori darajasida esa bu ikkisining tartibi "kompyuterga qarab" o'zgaradi.

Biz buni o'lchab ko'rdik. Ikki qatorli faylni (setTimeout va setImmediate) 100 marta ishga tushirdik: 92 marta setImmediate birinchi chiqdi, 8 marta — setTimeout. Bir xil kod, bir xil kompyuter — natija har xil. Nega?

Javob shu: Node'da "task navbati" bitta emas. Callback'lar turiga qarab fazalarga bo'lingan va event loop ularni doira bo'ylab aylanib o'tadi. Fazalarni bilsangiz, tartibni oldindan aytasiz.

Node.js nima darsida «Bahor» oshxonasini eslang: V8 — oshpaz, libuv — yordamchilar va texnika. Bugun oshpazning aylanma yo'lini ko'ramiz. U oshxonani doim bir xil tartibda aylanib chiqadi: avval qum soatlarga qaraydi, keyin eshikka, keyin "darhol" qog'ozlariga. Qaysi ish qachon bajarilishi shu yo'lga bog'liq.

2. Bitta aylanish: oltita faza

2.1 Doira

Avval rasmga qarang. Skript (faylingizning yuqori darajasi) bir marta bajariladi. Keyin libuv doirani aylana boshlaydi:

flowchart TB
  S["Skript bajariladi"] --> T["timers: setTimeout"]
  T --> P["pending: kechikkan I/O"]
  P --> I["idle, prepare: ichki"]
  I --> PO["poll: I/O kutish"]
  PO --> C["check: setImmediate"]
  C --> CL["close: yopilish"]
  CL --> Q{"Loop tirikmi?"}
  Q -->|ha| T
  Q -->|yo'q| E["Jarayon tugaydi"]

Nimaga qarang: o'q doim pastga va yana tepaga qaytadi. Poll'dan keyin har doim check keladi, check'dan keyin esa taymerlarga navbat faqat keyingi aylanishda keladi. Bugungi hamma tartib qoidalari shu bitta rasmdan chiqadi.

2.2 Har faza bir gapdan

Faza (phase) — event loop aylanishining bir bosqichi; har fazaning o'z callback navbati bor. Oshpaz har stansiyada o'sha stansiyaning ishlarini bajaradi, keyin keyingisiga o'tadi.

Faza Qaysi callback'lar «Bahor»da
timers vaqti kelgan setTimeout, setInterval qum soati tugaganmi?
pending callbacks oldingi aylanishdan qolgan ba'zi tizim I/O callback'lari kechagi qolgan ish
idle, prepare faqat Node'ning ichki ishi oshpaz qo'lini yuvadi
poll fayl, tarmoq callback'lari; yangi I/O'ni kutish eshik: yetkazib beruvchilar
check setImmediate "darhol keyin" qog'ozlari
close callbacks yopilgan resurslarning "close" hodisalari qozonlarni yig'ishtirish

Bu fazalarning hammasi libuv ichida. JavaScript kodingiz faqat callback'larda ishlaydi — oshpaz ishni oladi, bajaradi va qaytib aylanma yo'liga tushadi.

2.3 Aylanish (tick)

Loop aylanishi (loop iteration, tick) — doirani timers'dan close'gacha bir marta aylanib chiqish. Node hujjatlarida "tick" so'zi ko'p uchraydi va ko'pincha shu ma'noda ishlatiladi.

Bir ogohlantirish. process.nextTick nomidagi "tick" — aylanish emas. U hatto fazalarning birortasiga ham tegishli emas: o'z alohida navbati bor va har callback'dan keyin ishlaydi. Node'ning o'z qo'llanmasida ham "nomlar chalkash tanlangan" deyilgan. Buni keyingi darsda — Microtask navbati, process.nextTick va setImmediate — to'liq ochamiz. Bugun microtask va nextTick siz, faqat fazalar bilan ishlaymiz.

Tekshirib ko'ring: Server so'rov kutayotgan, hech qanday taymer yo'q. Event loop vaqtining ko'p qismini qaysi fazada o'tkazadi?

Javob

Poll fazasida. U yangi I/O (so'rov) kelishini kutadi. Taymer va setImmediate yo'q bo'lsa, poll'dan boshqa joyga shoshilishga sabab yo'q — loop shu yerda "uxlab" turadi va so'rov kelishi bilan uning callback'ini bajaradi.

3. Timers fazasi

3.1 Vaqti kelgan taymerlar

Timers fazasida libuv soatga qaraydi va vaqti o'tib bo'lgan taymerlarning callback'larini bajaradi. Vaqti kelmaganlari keyingi aylanishni kutadi.

Taymerlar darsidagi qoida Node'da ham amal qiladi: setTimeout(fn, 100) — "100 ms dan oldin emas", aniq 100 ms emas. Agar o'sha paytda boshqa callback ishlayotgan bo'lsa yoki loop poll'da band bo'lsa, taymer kechikadi.

3.2 setTimeout(fn, 0) aslida 1 ms

Node hujjatida yozilgan: kechikish 1 dan kichik bo'lsa, u 1 ga aylantiriladi. Demak setTimeout(fn, 0) va setTimeout(fn, 1) — Node uchun bir xil taymer. Bir xil vaqtli taymerlar esa qo'yilgan tartibda ishlaydi. Sinab ko'raylik:

js
setTimeout(() => console.log("1 ms"), 1);
setTimeout(() => console.log("0 ms"), 0);

Konsolda:

text
1 ms
0 ms

"1 ms" birinchi chiqdi, chunki u birinchi qo'yilgan — ikkalasi ham 1 ms. Brauzer esa 0 ni 0 deb qoldiradi. Xuddi shu kodni Chrome 155 da ishga tushirdik:

js
setTimeout(() => console.log("1 ms"), 1);
setTimeout(() => console.log("0 ms"), 0);

Chrome konsolida (biz ishga tushirdik, 3 marta bir xil):

text
0 ms
1 ms

Bu farq kundalik kodda deyarli sezilmaydi. Lekin u bitta muhim narsani ko'rsatadi: Node va brauzer bir xil API nomi bilan har xil qoidaga amal qilishi mumkin. Tartibga bog'liq kod yozsangiz, uni o'zi ishlaydigan muhitda sinang.

4. Poll fazasi — loop'ning kutish joyi

4.1 Poll nima qiladi

Poll (so'rash) fazasi ikki ish qiladi:

  1. Tayyor bo'lgan I/O hodisalarining callback'larini bajaradi: fayl o'qildi, tarmoqdan javob keldi, yangi ulanish ochildi.
  2. Ish qolmasa — yangi I/O hodisasini kutadi. Bu kutishda JavaScript ishlamaydi, CPU deyarli band emas.

Blocking va non-blocking I/O darsida ko'rgan readFile callback'i aynan shu fazada ishlaydi. Server so'rovlari ham shu yerga keladi — shuning uchun poll'ni event loop'ning yuragi deyishadi.

4.2 Poll qancha kutadi?

Poll ko'r-ko'rona kutmaydi. libuv kutish vaqtini doiradagi boshqa ishlarga qarab hisoblaydi (libuv hujjati, "Design overview"):

Holat Poll qancha kutadi
setImmediate navbatda bor umuman kutmaydi (0) — check fazasi kutyapti
Faol taymerlar bor eng yaqin taymer vaqtigacha
Taymer ham, setImmediate ham yo'q, lekin ochiq server bor cheksiz — yangi so'rov kelguncha
Hech qanday faol ish yo'q kutmaydi — loop tugaydi

Oshxonadagidek: oshpaz eshik oldida kutadi, lekin qum soati 5 daqiqadan keyin tugashini biladi. Shuning uchun u 5 daqiqadan ortiq kutmaydi — keyin qum soatiga qaytadi. "Darhol" qog'ozi bo'lsa, umuman kutmaydi.

4.3 Loop qachon tugaydi

Doiraning oxirida bitta savol bor: loop tirikmi (alive)? Loop tirik — unda hali kutadigan narsa bor: faol taymer, ochiq server yoki tugallanmagan fayl so'rovi. Hech narsa qolmasa, loop to'xtaydi va Node jarayoni o'zi tugaydi.

Shuning uchun oddiy skript oxiriga yetgach yopiladi, server esa yopilmaydi — server doim yangi ulanishni kutadi. Buni ko'rish uchun beforeExit hodisasidan foydalanamiz. U loop bo'shab, jarayon tugashidan oldin bir marta keladi (process.on — process obyekti darsida chuqur):

js
process.on("beforeExit", () => console.log("loop bo'sh"));
setTimeout(() => console.log("taymer 50 ms"), 50);
console.log("skript tugadi");

Konsolda:

text
skript tugadi
taymer 50 ms
loop bo'sh

Skript tugadi, lekin jarayon tugamadi: 50 ms li taymer loop'ni tirik ushlab turdi. Taymer ishlagach, loop'da hech narsa qolmadi — beforeExit keldi va jarayon yopildi.

Taymer loop'ni tirik ushlab turmasligi ham mumkin. unref() — "bu taymer uchun jarayonni ushlab turma" degani:

js
const taymer = setTimeout(() => console.log("taymer"), 1000);
taymer.unref();
console.log("skript tugadi");

Konsolda:

text
skript tugadi

"taymer" umuman chiqmadi. Loop'da unref qilingan taymerdan boshqa hech narsa yo'q edi — jarayon darhol tugadi. Biz o'lchadik: 1 soniya emas, 0.2 soniya. unref va ref ni timers va timers/promises chuqur darsida ishlatamiz: masalan, "har 10 soniyada log yoz, lekin faqat shu sabab bilan dasturni ochiq ushlama".

Tekshirib ko'ring: Skriptda setInterval(() => console.log("tik"), 1000) bor va boshqa hech narsa yo'q. Jarayon qachon tugaydi?

Javob

Hech qachon — o'zidan tugamaydi. setInterval faol taymer bo'lib qoladi va loop har doim tirik. To'xtatish uchun clearInterval chaqirish, taymerni unref() qilish yoki jarayonni tashqaridan to'xtatish (Ctrl+C) kerak.

5. Check fazasi va setImmediate

5.1 setImmediate — "poll'dan keyin, darhol"

setImmediate(fn) — callback'ni check fazasiga qo'yadi. Check har aylanishda poll'dan keyin keladi. Nomi "darhol" bo'lsa ham, u darhol emas: avval joriy kod tugaydi, keyin loop check fazasigacha yetadi.

setImmediate faqat Node'da (va Node'ga o'xshash runtime'larda) bor. Chrome konsolida typeof setImmediate — "undefined".

5.2 I/O callback ichida: check doim birinchi

Kodni qadamma-qadam bajarib ko'ring. Har qadamda navbatlarga qarang: callback qaysi fazaning navbatiga tushdi?

Xuddi shu kod oddiy blokda — o'z kompyuteringizda fazalar.mjs deb saqlab, node fazalar.mjs bilan ishga tushiring:

js
import { readFile } from "node:fs";

readFile(import.meta.filename, () => {
  setTimeout(() => console.log("timers: setTimeout"), 0);
  setImmediate(() => console.log("check: setImmediate"));
  console.log("poll: fayl o'qildi");
});

Konsolda:

text
poll: fayl o'qildi
check: setImmediate
timers: setTimeout

Biz bu faylni 30 marta ishga tushirdik — 30 marta ham aynan shu tartib. Bu tasodif emas, kafolat: poll'dan check'ga yo'l timers'dan o'tmaydi.

5.3 Taymer ichida: yana check birinchi

Endi ikkalasini taymer callback'i ichida qo'yamiz. Biz timers fazasidamiz:

js
setTimeout(() => {
  setTimeout(() => console.log("setTimeout 0"), 0);
  setImmediate(() => console.log("setImmediate"));
}, 0);

Konsolda:

text
setImmediate
setTimeout 0

Doiraga qarang: timers → pending → idle/prepare → poll → check → close → timers. Check shu aylanishning o'zida keladi, yangi taymer esa keyingi aylanishni kutadi. Poll ham bu yerda kutib o'tirmaydi — navbatda setImmediate bor (poll kutish jadvalidagi birinchi qator). 30 ta ishga tushirishdan 30 tasida shu tartib chiqdi.

5.4 Faylning yuqori darajasida: kafolat yo'q

Endi eng mashhur holat — ikkalasi faylning yuqori darajasida:

js
setTimeout(() => process.stdout.write("T"), 0);
setImmediate(() => process.stdout.write("I"));

process.stdout.write — console.log ga o'xshaydi, lekin yangi qatorsiz yozadi: IT yoki TI chiqadi. Natija oldindan ma'lum emas, shuning uchun bu blokning chiqishini yozmadik — o'lchadik:

Yuqori darajada setImmediate birinchi chiqqan ulush
  • ti.mjs, 100 marta92 IT, 8 TI92 %
  • ti.cjs, 100 marta89 IT, 11 TI89 %
  • node -e, 20 martakanon: 19 IT, 1 TI95 %
  • oldin 5 ta console.log30 dan 30 TI0 %

Manba: IlmHamroh o'lchovi, 2026-10-23: Node 24.21.0, Windows 11, i5-12500H; har safar yangi jarayon

Nimaga qarang: oxirgi ustun. Kod oldidan bir nechta console.log qo'shganimizda setTimeout 30 martadan 30 marta birinchi bo'ldi. Sababi — o'sha "1 ms":

  1. Skript tugagach, loop timers fazasiga keladi va soatga qaraydi.
  2. Skript ishga tushganidan beri 1 ms o'tgan bo'lsa — taymer tayyor, T birinchi.
  3. 1 ms hali o'tmagan bo'lsa — taymer o'tkazib yuboriladi, loop poll va check'ga o'tadi, I birinchi.

Ya'ni tartib skript qancha ishlaganiga va kompyuter qanchalik bandligiga bog'liq. Bunday tartibga hech qachon tayanmang. Qaysi biri birinchi bo'lishi muhim bo'lsa, ikkalasini bitta I/O yoki taymer callback'i ichiga qo'ying — u yerda tartib kafolatlangan.

Tekshirib ko'ring: setImmediate ichida yana setImmediate qo'yilsa, ichkisi shu check fazasida ishlaydimi yoki keyingi aylanishdami?

Javob

Keyingi aylanishda. Check fazasi boshida navbatda turganlarni bajaradi; ular ishlayotganda qo'shilganlari keyingi aylanishning check fazasiga qoladi. Shuning uchun setImmediate zanjiri loop'ni qotirmaydi — har aylanishda taymerlar va I/O'ga ham navbat tegadi. Bu xususiyat og'ir ishni bo'laklashda ishlatiladi (Event loop'ni bloklash).

6. Qolgan fazalar: pending, idle/prepare, close

6.1 Pending callbacks

Pending callbacks (kechiktirilgan callback'lar) — oldingi aylanishda bajarib ulgurilmagan ba'zi tizim I/O callback'lari. Node hujjati misol qilib TCP xatolarini keltiradi: ba'zi tizimlar ulanish xatosini (ECONNREFUSED) darhol emas, shu fazada xabar qiladi. Bu fazani kodingizdan boshqara olmaysiz va deyarli o'ylamaysiz.

6.2 Idle va prepare

Idle, prepare — libuv va Node'ning ichki ishlari. Prepare — poll kutishga kirishidan oldin bajariladigan tayyorgarlik. Ularga JavaScript'dan callback qo'yib bo'lmaydi; ro'yxatda ular faqat doirani to'liq ko'rsatish uchun.

6.3 Close callbacks

Close callbacks (yopilish callback'lari) — resurs to'satdan yopilganda keladigan "close" hodisalari, masalan tarmoq ulanishi (socket.destroy()). Ulanish va hodisalar bilan EventEmitter asoslari va keyingi tarmoq darslarida ishlaymiz.

Bu fazaning tartibiga ham tayanmang. Biz I/O callback ichida socket'ni yopib, yoniga setTimeout(0) va setImmediate qo'ydik va 30 marta ishga tushirdik. 29 marta "close" taymerdan keyin keldi, 1 marta — oldin. Sabab: Windows'da ulanish yopilishi alohida asinxron ish, u bir necha aylanish olishi mumkin. Linux'da sinamadik; tartib u yerda boshqacha bo'lishi mumkin. Qoida bir xil: "close" hodisasi "qachondir, yopilish tugaganda" keladi.

6.4 libuv hujjati va Node qo'llanmasi

Node qo'llanmasi doirani timers'dan boshlaydi — biz ham shunday chizdik. libuv hujjatida esa taymerlar har aylanish oxirida, close'dan keyin ishlaydi. Bu bitta doiraning ikki xil chizilishi: tartib ham, bugungi qoidalar ham o'zgarmaydi.

7. Brauzer bilan taqqoslash

Event loop darsidagi jadvalni endi to'liq qilamiz:

Brauzer Node
Task navbati asosiy navbat (+ ustuvorliklar) fazalar: timers, poll, check …
setTimeout(fn, 0) 0 ms (ichma-ich 5 dan keyin 4 ms) 1 ms
setImmediate yo'q check fazasi
Ekranni chizish bor yo'q
Loop tugaydimi sahifa yopilguncha yashaydi ish qolmasa jarayon tugaydi

Umumiy qism ham katta. Microtask navbati ikkala joyda ham har callback'dan keyin to'liq bo'shatiladi. Taymer ichidagi Promise ikkala muhitda bir xil tartib beradi:

js
setTimeout(() => {
  console.log("taymer");
  Promise.resolve().then(() => console.log("microtask"));
}, 0);
console.log("sinxron");

Konsolda:

text
sinxron
taymer
microtask

Saytda «Ishga tushir» bilan sinang — brauzer ham, Node 24 ham shu tartibni beradi.

8. CLI qadami: valyuta da fazalar

Bu darsda valyuta ga kod qo'shilmaydi va commit yo'q — loyiha process obyekti darsida boshlanadi. Yakuniy valyuta bitta buyruqni bajarganda loop'da nima bo'ladi:

Kodda Faza
fetch bilan CBU kurslarini so'rash — javob kutish poll
keshni fayldan o'qish va yozish poll (fayl ishi thread pool'da, callback — poll'da)
qayta urinish oldidan kutish (setTimeout) timers
javob chiqdi, ish qolmadi loop bo'sh → jarayon tugaydi

Kanon kodini qidirdik: 19 ta manba faylida ham, testlarda ham birorta setImmediate yo'q. CLI bitta buyruqni bajaradi — ishni keyingi aylanishga qoldirish ehtiyoji yo'q. CLI uchun muhim fakt boshqa: ish tugagach jarayon o'zi yopilishi kerak. Yopilmay qolgan taymer yoki ochiq ulanish valyuta kurs USD ni javobdan keyin ham osiltirib qo'yadi. Kanonda bunga o'xshash holat testlarda chiqdi: kechikkan ulanish soxta serverning close() ini ushlab turdi — buni timers va timers/promises chuqur darsida ko'ramiz.

Kanon tajribasi (t03-tartib) ham shuni berdi: I/O ichida setImmediate → setTimeout 0, yuqori darajada 20 jarayondan 19 tasida setImmediate birinchi.

9. Ko'p uchraydigan xatolar

9.1 "setImmediate — darhol"

Nomi aldaydi. setImmediate joriy kodni to'xtatmaydi va hatto microtask'lardan ham keyin keladi. U "shu aylanishning check fazasida" degani. Haqiqatan eng tez navbat — process.nextTick va microtask'lar (Microtask navbati, process.nextTick va setImmediate).

9.2 Yuqori darajadagi tartibga tayanish

setTimeout(fn, 0) va setImmediate(fn) ni faylning yuqori darajasida yozib, "mening kompyuterimda doim setImmediate birinchi" deb kod yozish — xato. Grafikdagi to'rtinchi ustun ko'rsatdi: bir necha qator qo'shilsa, tartib butunlay teskari bo'ladi. Tartib kerak bo'lsa — ikkalasini bitta callback ichiga qo'ying yoki bittasini boshqasining ichidan chaqiring.

9.3 "Skript tugadi, lekin terminal qaytmayapti"

Dastur natija chiqardi, lekin jarayon tugamaydi. Sabab deyarli doim — loop'ni tirik ushlab turgan narsa: setInterval, ochiq server, yopilmagan ulanish. Yechim: uni yoping (clearInterval, server.close()) yoki ataylab ushlab turmaslik kerak bo'lsa, unref() qiling. process.exit() bilan "kesib tashlash" — oxirgi chora: u yozilmay qolgan chiqishni yo'qotishi mumkin (process obyekti).

9.4 "Taymer aniq vaqtida ishlaydi"

setTimeout(fn, 100) — kamida 100 ms. Loop og'ir sinxron hisob bilan band bo'lsa, taymer kechikadi: kanonda bitta sikl uni 1 390 ms gacha kechiktirdi (Event loop'ni bloklash).

9.5 Brauzerda setImmediate yoki process

Node uchun yozilgan tartib misolini brauzerga ko'chirsangiz:

text
Uncaught ReferenceError: setImmediate is not defined

Tarjimasi: "ushlanmagan xato: setImmediate aniqlanmagan". Brauzerda check fazasi ham, setImmediate ham yo'q. Brauzer uchun "keyingi task'da" kerak bo'lsa — setTimeout(fn, 0).

10. Mashqlar

Mashqlar kurs/mashqlar/24/03-event-loop-fazalari/ da. Har kodni .mjs faylga saqlab, node fayl.mjs bilan tekshiring.

1-mashq (oson): fazalarni joyiga qo'ying

  • setImmediate callback'lari ishlaydigan faza — .
  • readFile callback'i ishlaydigan faza — .
  • Vaqti kelgan setTimeout callback'lari — .
  • Socket yopilganda "close" hodisasi — .
  • Node'da setTimeout(fn, 0) ning haqiqiy kechikishi, ms — .
Yechim

check — poll'dan keyingi faza, setImmediate uchun. poll — fayl va tarmoq callback'lari. timers — vaqti o'tgan taymerlar. close — yopilish hodisalari. 1 — Node hujjati bo'yicha 1 dan kichik kechikish 1 ga aylanadi.

2-mashq (o'rta): tartibni oldindan ayting

Har kod nima chiqarishini ishga tushirmasdan yozing. Agar tartib kafolatlanmagan bo'lsa — shunday deb yozing. Keyin ishga tushirib tekshiring (uchinchisini 10 marta).

A:

js
import { readFile } from "node:fs";
readFile(import.meta.filename, () => {
  setImmediate(() => console.log("A1"));
  setTimeout(() => console.log("A2"), 0);
  setImmediate(() => console.log("A3"));
});

B:

js
setTimeout(() => {
  console.log("B1");
  setImmediate(() => console.log("B2"));
  setTimeout(() => console.log("B3"), 0);
}, 0);

C:

js
setImmediate(() => console.log("C1"));
setTimeout(() => console.log("C2"), 0);
Yechim

A — A1, A3, A2. Ikkala setImmediate shu aylanishning check fazasida, qo'yilgan tartibda. Taymer keyingi aylanishda.

js
import { readFile } from "node:fs";
readFile(import.meta.filename, () => {
  setImmediate(() => console.log("A1"));
  setTimeout(() => console.log("A2"), 0);
  setImmediate(() => console.log("A3"));
});

Konsolda:

text
A1
A3
A2

B — B1, B2, B3. B1 — taymer callback'ining o'zi. Undan keyin loop shu aylanishning check fazasiga yetadi (B2), yangi taymer esa keyingi aylanishda (B3).

js
setTimeout(() => {
  console.log("B1");
  setImmediate(() => console.log("B2"));
  setTimeout(() => console.log("B3"), 0);
}, 0);

Konsolda:

text
B1
B2
B3

C — kafolatlanmagan. Faylning yuqori darajasida taymer 1 ms bo'lgani uchun natija skript qancha vaqt ishlaganiga bog'liq. 10 marta ishga tushirsangiz, ko'pincha C1 birinchi, ba'zan C2 — sizning kompyuteringizda nisbat boshqacha bo'lishi mumkin.

3-mashq (qiyin): keyingiAylanish() yordamchisi

await bilan "loop'ning keyingi check fazasigacha kut" deyish uchun funksiya yozing: keyingiAylanish() Promise qaytarsin va u setImmediate orqali bajarilsin. Ishora: Promise asoslari dagi new Promise((resolve) => ...) va setImmediate(resolve) yetadi.

Keyin uni I/O callback'i ichida tekshiring: callback'da avval setTimeout(() => console.log("taymer"), 0) qo'ying, keyin await keyingiAylanish() qilib "keyingi aylanish" deb yozing. Qaysi biri birinchi chiqadi va nega?

Yechim
js
import { readFile } from "node:fs";

function keyingiAylanish() {
  return new Promise((resolve) => setImmediate(resolve));
}

readFile(import.meta.filename, async () => {
  setTimeout(() => console.log("taymer"), 0);
  console.log("poll: callback");
  await keyingiAylanish();
  console.log("keyingi aylanish");
});

Konsolda:

text
poll: callback
keyingi aylanish
taymer

setImmediate(resolve) check fazasida ishlaydi va Promise'ni bajaradi. await dan keyingi kod — microtask: u o'sha setImmediate callback'idan keyin darhol ishlaydi, hali check fazasida. Taymer esa keyingi aylanishning timers fazasini kutadi. async callback'ni readFile ga berish mumkin: readFile qaytgan Promise'ga qaramaydi, faqat funksiyani chaqiradi. Bu yordamchini Event loop'ni bloklash darsida uzun hisobni bo'laklashda ishlatamiz: har bo'lakdan keyin await keyingiAylanish() — loop boshqa ishlarga ham vaqt topadi.

4-mashq: CLI qadami — tartibni o'z kompyuteringizda o'lchang

valyuta ga bu darsda kod qo'shilmaydi. Kanon tajribasini o'zingizda takrorlang:

  1. ti.mjs fayliga «Faylning yuqori darajasida: kafolat yo'q» bo'limidagi ikki qatorni yozing (setTimeout va setImmediate, process.stdout.write bilan).
  2. Uni 20 marta ishga tushiring. Git Bash'da: for i in $(seq 20); do node ti.mjs; echo; done. Necha marta IT, necha marta TI chiqdi?
  3. Fayl boshiga 5 ta console.log("salom") qo'shib, yana 20 marta ishga tushiring. Nima o'zgardi?
Yechim

Kanon mashinasida (Node 24.21.0, Windows 11, i5-12500H) 100 dan 92 marta IT chiqdi, 5 ta console.log bilan esa 30 dan 30 marta TI. Sizda raqamlar boshqacha bo'lishi mumkin — xulosa raqamda emas, "tartib kafolatlanmagan" qoidasida. console.log vaqt oladi: loop timers fazasiga yetganda 1 ms o'tib bo'lgan, taymer birinchi chiqadi.

11. Real ishda

  • Intervyu: "Node event loop fazalarini sanab bering", "setImmediate va setTimeout(0) farqi?", "Poll fazasi nima qiladi?" — Node backend intervyularining doimiy savollari. Yaxshi javob fazalarni tartib bilan aytadi va "I/O ichida kafolatlangan, yuqori darajada yo'q" farqini ko'rsatadi.
  • Serverlar: Express, Fastify, NestJS so'rovlari poll fazasida keladi. Bitta callback uzoq ishlasa, boshqa so'rovlar va taymerlar kutadi — shuning uchun server kodida sinxron og'ir ish taqiqlanadi (Event loop'ni bloklash).
  • Osilib qolgan jarayonlar: testlar tugagach jarayon yopilmasa (yopilmagan server, ulanish, setInterval), test yurgizgich ogohlantiradi yoki kutib qoladi. Sababini topish — "loop'ni nima tirik ushlab turibdi?" savoli.

Xulosa

  • Node event loop — fazalar doirasi: timers → pending → idle/prepare → poll → check → close.
  • Poll — kutish joyi: setImmediate bo'lsa kutmaydi, taymer bo'lsa eng yaqin taymergacha, ochiq server bo'lsa cheksiz.
  • I/O yoki taymer callback'i ichida setImmediate doim setTimeout(0) dan oldin; yuqori darajada tartib kafolatlanmagan (biz 92/8 va 0/30 ni o'lchadik).
  • Node'da setTimeout(fn, 0) — aslida 1 ms; brauzerda — 0 ms.
  • Loop'da faol ish qolmasa jarayon tugaydi; setInterval, ochiq server uni tirik ushlab turadi, unref() — ushlab turma degani.

Keyingi dars: Microtask navbati, process.nextTick va setImmediate — microtask'lar fazalar orasida qayerda ishlaydi, process.nextTick nega .cjs faylda Promise'dan oldin, .mjs da esa keyin.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
Node.js event loop fazalari: timers, poll, check va bitta aylanish — IlmHamroh