Event Loop fazalari — timers, poll, check, close

~20 daq · Node.js · Event Loop

Muammodan boshlaylik

Oldingi darsda Event Loop ning umumiy rasmini ko'rdik. Lekin amalda shunday savollar chiqadi:

  • setTimeout(fn, 0) va setImmediate(fn) — qaysi birinchi ishlaydi?
  • Nima uchun ba'zan setTimeout dan oldin setImmediate ishlaydi?
  • fs.readFile callback qaysi fazada ishlaydi?

Bu savollarga javob berish uchun Event Loop ning fazalarini bilish kerak.


Fazalar — to'liq rasm

Node.js Event Loop 6 ta fazadan iborat. Har bir faza o'z navbatini to'liq bajaradi, keyin keyingi fazaga o'tadi.

┌─────────────────────────────────────────────────────┐
│                                                     │
│   ┌──────────┐                                      │
│   │  timers  │  ← setTimeout, setInterval           │
│   └────┬─────┘                                      │
│        │                                            │
│   ┌────▼──────────────┐                             │
│   │ pending callbacks │  ← I/O xatolari             │
│   └────┬──────────────┘                             │
│        │                                            │
│   ┌────▼──────────────┐                             │
│   │  idle, prepare    │  ← ichki (siz ko'rmaysiz)   │
│   └────┬──────────────┘                             │
│        │                                            │
│   ┌────▼──────────────┐                             │
│   │      poll         │  ← I/O callbacklar          │
│   └────┬──────────────┘                             │
│        │                                            │
│   ┌────▼──────────────┐                             │
│   │      check        │  ← setImmediate             │
│   └────┬──────────────┘                             │
│        │                                            │
│   ┌────▼──────────────┐                             │
│   │ close callbacks   │  ← socket.on('close', ...)  │
│   └────┬──────────────┘                             │
│        │                                            │
│        └──────────────────────────────────────────► │
│                    (qaytadan timers)                 │
└─────────────────────────────────────────────────────┘

Har bir faza o'tgandan keyin — microtasklar (nextTick, Promise) bajariladi.


1. Timers fazasi

Bu fazada setTimeout va setInterval callbacklari bajariladi.

Lekin muhim nuance bor: setTimeout(fn, 100) — "100ms dan keyin bajar" degani emas. "100ms o'tgandan keyin, timers fazasiga kelganda bajar" degani.

setTimeout(() => {
  console.log('100ms o\'tdi');
}, 100);

Agar timers fazasiga kelganda 100ms o'tmagan bo'lsa — bu callback keyingi aylanishda bajariladi.

setTimeout(fn, 0) haqida haqiqat

setTimeout(() => console.log('setTimeout'), 0);

0 ms deb yozsangiz ham — Node.js bu ni kamida 1ms deb qabul qiladi. Va u darhol ishlamaydi — timers fazasiga kelganda ishlaydi.


2. Pending Callbacks fazasi

Bu fazada oldingi aylanishda bajarilmay qolgan I/O callbacklari ishlaydi. Asosan TCP socket xatolari shu yerda qayta ishlanadi.

Siz bu fazani to'g'ridan-to'g'ri boshqarmaysiz — Node.js o'zi hal qiladi.


3. Poll fazasi — eng muhim faza

Bu — Event Loop ning "yurak urishi". Eng ko'p vaqt shu fazada o'tadi.

Poll fazasi ikki ish qiladi:

Birinchi: I/O callbacklarini bajaradi. fs.readFile, http.request, ma'lumotlar bazasi so'rovlari — bularning callbacklari shu yerda ishlaydi.

Ikkinchi: Agar navbat bo'sh bo'lsa — kutadi. Yangi I/O event kelishini kutadi. Lekin qancha kutadi?

Poll fazasida kutish qoidasi:
  - timers navbatida callback bor? → kutmaydi, timers fazasiga o'tadi
  - setImmediate bor? → kutmaydi, check fazasiga o'tadi
  - Ikkalasi ham yo'q? → yangi I/O event kelguncha kutadi

Bu kutish — Node.js ning "uxlashi". CPU ni bo'sh qoldirmaydi, yangi event kelganda uyg'onadi.

Amaliy misol

const fs = require('fs');

fs.readFile('fayl.txt', (err, data) => {
  // Bu callback — poll fazasida ishlaydi
  console.log('Fayl o\'qildi');
});

setTimeout(() => {
  console.log('setTimeout');
}, 0);

Natija:

setTimeout
Fayl o'qildi

Nima uchun setTimeout oldin? Chunki:

1. timers fazasi → setTimeout callback bor → ishlaydi
2. poll fazasi → fs.readFile callback keldi → ishlaydi

4. Check fazasi — setImmediate

Bu fazada faqat setImmediate callbacklari ishlaydi.

setImmediate — "poll fazasi tugagandan keyin, shu aylanishda bajar" degani.

const fs = require('fs');

fs.readFile('fayl.txt', () => {
  setTimeout(() => console.log('setTimeout'), 0);
  setImmediate(() => console.log('setImmediate'));
});

Natija — har doim:

setImmediate
setTimeout

Nima uchun? fs.readFile callback poll fazasida ishlaydi. Poll fazasidan keyin — check fazasi keladi. Shuning uchun setImmediate har doim oldin ishlaydi.

setTimeout vs setImmediate — tashqi kontekstda

// fs.readFile ICHIDA EMAS — tashqi kontekstda
setTimeout(() => console.log('setTimeout'), 0);
setImmediate(() => console.log('setImmediate'));

Bu holda natija noaniq — ba'zan setTimeout oldin, ba'zan setImmediate oldin. Nima uchun?

Sabab: Dastur boshlanayotganda qaysi fazadan boshlanishi
aniq emas. Agar timers fazasiga kelganda 1ms o'tmagan
bo'lsa → setImmediate oldin.
Agar 1ms o'tgan bo'lsa → setTimeout oldin.

Shuning uchun setImmediate vs setTimeout ni tashqi kontekstda solishtirmang — natija kafolatlanmagan.


5. Close Callbacks fazasi

Bu fazada yopilish callbacklari ishlaydi.

const net = require('net');

const server = net.createServer();
server.on('close', () => {
  console.log('Server yopildi'); // close callbacks fazasida
});
server.close();

socket.destroy(), server.close() — bularning callbacklari shu fazada ishlaydi.


Microtasklar — fazalar orasida

Har bir faza tugagandan keyin — microtasklar navbati to'liq bajariladi. Keyin keyingi fazaga o'tiladi.

timers fazasi tugadi
    ↓
nextTick navbati bajariladi (to'liq)
    ↓
Promise navbati bajariladi (to'liq)
    ↓
pending callbacks fazasi boshlanadi
    ↓
...

Muhim misol

const fs = require('fs');

fs.readFile('fayl.txt', () => {
  setTimeout(() => console.log('1 setTimeout'), 0);
  setImmediate(() => console.log('2 setImmediate'));

  Promise.resolve().then(() => console.log('3 Promise'));
  process.nextTick(() => console.log('4 nextTick'));
});

Natija:

4 nextTick
3 Promise
2 setImmediate
1 setTimeout

Tartib:

fs.readFile callback (poll fazasida) ishlaydi
    ↓
Poll fazasi tugadi → microtasklar:
    → nextTick: "4 nextTick"
    → Promise: "3 Promise"
    ↓
Check fazasi:
    → setImmediate: "2 setImmediate"
    ↓
Microtasklar (bo'sh)
    ↓
Keyingi aylanish — timers fazasi:
    → setTimeout: "1 setTimeout"

Fazalarni amalda his qilish

console.log('--- Sinxron kod ---');

// Timers fazasi
setTimeout(() => console.log('[timers] setTimeout 100ms'), 100);
setTimeout(() => console.log('[timers] setTimeout 0ms'), 0);

// Check fazasi
setImmediate(() => console.log('[check] setImmediate'));

// Poll fazasi
const fs = require('fs');
fs.readFile(__filename, () => {
  console.log('[poll] fs.readFile callback');

  // Poll ichidan
  setTimeout(() => console.log('[poll→timers] setTimeout'), 0);
  setImmediate(() => console.log('[poll→check] setImmediate'));
});

// Microtasklar
process.nextTick(() => console.log('[microtask] nextTick'));
Promise.resolve().then(() => console.log('[microtask] Promise'));

console.log('--- Sinxron kod tugadi ---');

Natija:

--- Sinxron kod ---
--- Sinxron kod tugadi ---
[microtask] nextTick
[microtask] Promise
[timers] setTimeout 0ms
[check] setImmediate
[poll] fs.readFile callback
[poll→check] setImmediate
[poll→timers] setTimeout
[timers] setTimeout 100ms

Xulosa — qaysi faza nima qiladi

Faza Nima ishlaydi Siz boshqarasizmi?
timers setTimeout, setInterval Ha
pending callbacks I/O xatolari Yo'q
idle, prepare Ichki Yo'q
poll fs, http, DB callbacklar Bilvosita
check setImmediate Ha
close callbacks socket.on('close') Ha

Microtasklar (har faza orasida):

  1. process.nextTick — eng birinchi
  2. Promise.then — ikkinchi

Qoida: setImmediate ni I/O callback ichida ishlatganda — u setTimeout(fn, 0) dan har doim oldin ishlaydi.


Keyingi dars

8-dars: setImmediate vs setTimeout vs process.nextTick — farqlari