setImmediate vs setTimeout vs process.nextTick — farqlari

~20 daq · Node.js · Event Loop

Muammodan boshlaylik

Siz asinxron kod yozyapsiz. Biror ishni "keyinroq" bajarish kerak — lekin qanchalik keyinroq? Darhol keyin? Keyingi iteratsiyada? Belgilangan vaqtdan keyin?

Node.js da buning uchun uchta funksiya bor: process.nextTick, setImmediate, setTimeout. Ko'rinishda o'xshash — lekin ishlash tartibi butunlay boshqacha. Noto'g'ri tanlasangiz — kutilmagan natijalar chiqadi.


Uchala funksiya — qisqacha

process.nextTick(() => {
  // Hozirgi operatsiya tugagandan keyin,
  // Event Loop ning keyingi fazasiga O'TMASDAN OLDIN ishlaydi
});

Promise.resolve().then(() => {
  // process.nextTick dan keyin,
  // lekin hali ham keyingi fazaga o'tmasdan ishlaydi
});

setImmediate(() => {
  // Event Loop ning check fazasida ishlaydi
  // (poll fazasidan keyin)
});

setTimeout(() => {
  // Event Loop ning timers fazasida ishlaydi
  // (belgilangan ms o'tgandan keyin)
}, 0);

process.nextTick — "hoziroq, lekin keyin"

process.nextTick — Event Loop fazalari orasida emas, faza ichida ishlaydi. Hozirgi sinxron kod tugagandan keyin, keyingi fazaga o'tmasdan oldin — shu yerda ishlaydi.

Sinxron kod ishlaydi
    ↓
process.nextTick navbati to'liq bajariladi
    ↓
Promise microtask navbati to'liq bajariladi
    ↓
Event Loop keyingi fazaga o'tadi

Misol

console.log('1 — sinxron');

process.nextTick(() => {
  console.log('2 — nextTick');
});

console.log('3 — sinxron');

Natija:

1 — sinxron
3 — sinxron
2 — nextTick

nextTick callback sinxron kod tugagandan keyin, lekin boshqa hech narsadan oldin ishladi.

Nima uchun kerak?

process.nextTick ning asosiy maqsadi — foydalanuvchiga event handler qo'shish imkonini berish. Masalan, siz class yozyapsiz. Constructor ichida event chiqarsangiz — foydalanuvchi hali handler qo'sha olmagan:

const EventEmitter = require('events');

class Fayl extends EventEmitter {
  constructor(nom) {
    super();
    // ❌ Xato — foydalanuvchi hali 'ready' ga subscribe qilmagan
    this.emit('ready', nom);
  }
}

const f = new Fayl('test.txt');
f.on('ready', (nom) => console.log(nom)); // hech qachon ishlamaydi!

process.nextTick bilan to'g'ri yozish:

class Fayl extends EventEmitter {
  constructor(nom) {
    super();
    // ✅ To'g'ri — sinxron kod tugagandan keyin emit qiladi
    // Foydalanuvchi shu orada handler qo'sha oladi
    process.nextTick(() => this.emit('ready', nom));
  }
}

const f = new Fayl('test.txt');
f.on('ready', (nom) => console.log(nom)); // ishlaydi!

Xavfli tomoni

process.nextTick ichida yana process.nextTick chaqirsangiz — Event Loop hech qachon keyingi fazaga o'ta olmaydi. I/O to'xtaydi, server javob bermay qoladi.

// ❌ Xavfli — cheksiz loop
function recursive() {
  process.nextTick(recursive);
}
recursive();
// Bu kod I/O ni butunlay bloklaydi!

setImmediate — "poll dan keyin"

setImmediate — Event Loop ning check fazasida ishlaydi. Poll fazasi tugagandan keyin, shu aylanishda bajariladi.

poll fazasi tugadi
    ↓
microtasklar (nextTick, Promise)
    ↓
check fazasi → setImmediate ishlaydi
    ↓
close callbacks
    ↓
keyingi aylanish...

Misol

setImmediate(() => {
  console.log('setImmediate');
});

console.log('sinxron');

Natija:

sinxron
setImmediate

Nima uchun kerak?

setImmediate ning asosiy maqsadi — I/O callback ichida "shu aylanishda, lekin I/O dan keyin" biror ish bajarish.

const fs = require('fs');

fs.readFile('fayl.txt', () => {
  // Fayl o'qildi. Endi qo'shimcha ish qilish kerak.
  // Bu ish shu aylanishda, lekin boshqa I/O lardan keyin bo'lsin.
  setImmediate(() => {
    console.log('Qo\'shimcha ish bajarildi');
  });
});

Katta hajmli ma'lumot qayta ishlashda ham foydali — har iteratsiyada setImmediate bilan Event Loop ga "nafas olish" imkonini berish:

function kattaMassivniQayta(massiv, index = 0) {
  if (index >= massiv.length) {
    console.log('Tugadi');
    return;
  }

  // Bitta elementni qayta ishla
  qaytaIshla(massiv[index]);

  // Keyingi elementni keyingi iteratsiyada bajar
  // Bu Event Loop ni bloklamaydi
  setImmediate(() => kattaMassivniQayta(massiv, index + 1));
}

setTimeout — "N ms dan keyin"

setTimeout — Event Loop ning timers fazasida ishlaydi. Belgilangan millisekund o'tgandan keyin, timers fazasiga kelganda bajariladi.

setTimeout(() => {
  console.log('1 soniyadan keyin');
}, 1000);

setTimeout(fn, 0) — nima bu?

0 ms deb yozsangiz ham — bu "darhol" degani emas. Node.js 0 ni kamida 1ms deb qabul qiladi. Va u timers fazasiga kelganda ishlaydi — bu bir necha ms kechikishi mumkin.

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

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

Dastur boshlanayotganda:
  - Agar timers fazasiga kelganda 1ms o'tgan bo'lsa
    → setTimeout oldin ishlaydi
  - Agar 1ms o'tmagan bo'lsa
    → setImmediate oldin ishlaydi (check fazasi)

Bu tizim yukiga, protsessor tezligiga bog'liq.
Kafolatlanmagan.

Uchala funksiyani solishtirish

console.log('--- Boshlanish ---');

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

setImmediate(() => console.log('setImmediate'));

process.nextTick(() => console.log('nextTick'));

Promise.resolve().then(() => console.log('Promise'));

console.log('--- Tugash ---');

Natija:

--- Boshlanish ---
--- Tugash ---
nextTick
Promise
setTimeout     ← yoki setImmediate (noaniq)
setImmediate   ← yoki setTimeout (noaniq)

Sinxron kod tugagandan keyin:

  1. nextTick — eng birinchi (microtask, yuqori ustuvorlik)
  2. Promise — ikkinchi (microtask)
  3. setTimeout va setImmediate — tartib noaniq (tashqi kontekstda)

I/O ichida — tartib aniq

const fs = require('fs');

fs.readFile(__filename, () => {
  console.log('--- I/O callback ---');

  setTimeout(() => console.log('setTimeout'), 0);
  setImmediate(() => console.log('setImmediate'));
  process.nextTick(() => console.log('nextTick'));
  Promise.resolve().then(() => console.log('Promise'));
});

Natija — har doim bir xil:

--- I/O callback ---
nextTick
Promise
setImmediate
setTimeout

I/O callback poll fazasida ishlaydi. Poll tugagandan keyin:

  1. nextTick (microtask)
  2. Promise (microtask)
  3. setImmediate (check fazasi — poll dan keyin)
  4. setTimeout (keyingi aylanish — timers fazasi)

Qachon qaysinisini ishlatish kerak?

process.nextTick:
  ✓ Hozirgi operatsiya tugagandan keyin, lekin
    Event Loop fazasi o'zgarmasdan oldin bajarish kerak
  ✓ Event emitter da emit ni kechiktirish
  ✓ Xatoni asinxron qaytarish (API consistency)
  ✗ Rekursiv ishlatmang — I/O ni bloklaydi

setImmediate:
  ✓ I/O callback ichida "shu aylanishda, lekin keyin"
  ✓ Katta hisoblashni bo'laklarga bo'lish
  ✓ setTimeout(fn, 0) o'rniga I/O ichida
  ✓ Natija kafolatlangan (I/O ichida)

setTimeout(fn, 0):
  ✓ Brauzer bilan mos kod (universal)
  ✓ Minimal kechikish kerak bo'lganda
  ✗ setImmediate bilan solishtirmang — noaniq

Amaliy qoida

I/O callback ichida "keyinroq" bajarish kerak?
  → setImmediate ishlating

Hozirgi sinxron blok tugagandan keyin darhol?
  → process.nextTick ishlating

Belgilangan vaqtdan keyin?
  → setTimeout ishlating

Xulosa

process.nextTick setImmediate setTimeout(fn, 0)
Qayerda ishlaydi Faza orasida check fazasi timers fazasi
Ustuvorlik Eng yuqori O'rta O'rta
I/O ichida tartib 1-chi 2-chi 3-chi
Tashqi kontekstda 1-chi Noaniq Noaniq
Xavf Rekursiyada bloklaydi Xavfsiz Xavfsiz

Eng muhim qoida: setImmediate va setTimeout(fn, 0) ni tashqi kontekstda solishtirmang — natija kafolatlanmagan. I/O ichida esa setImmediate har doim setTimeout dan oldin ishlaydi.


Keyingi dars

9-dars: Blocking vs Non-blocking — nima uchun xavfli