IlmHamroh
JavaScript Full-stack/24-qism. Node.js4/55-dars21 daqiqa
Mundarija (40)

process.nextTick va microtask navbati Node'da: nextTick, queueMicrotask va setImmediate tartibi

Qisqacha: Node'da har JavaScript callback'idan keyin ikki navbat bo'shatiladi: avval process.nextTick navbati, keyin microtask navbati (Promise, queueMicrotask) — shundan keyingina event loop keyingi callback yoki fazaga o'tadi. Shuning uchun .cjs faylda nextTick Promise'dan oldin ishlaydi. ESM (.mjs) faylning yuqori darajasida esa teskari: modul microtask navbati bo'shatilayotgan paytda bajariladi, nextTick Promise'dan keyin chiqadi. setImmediate — bularning hech biriga kirmaydi, u check fazasida.

Bu darsda

  • process.nextTick navbati va microtask navbati qachon va qaysi tartibda bo'shatilishini ayta olasiz.
  • Bir xil kod .cjs va .mjs faylda nega har xil tartib berishini tushuntira olasiz.
  • nextTick, queueMicrotask, setImmediate va setTimeout(0) ni bitta jadvalda taqqoslaysiz.
  • nextTick va Promise zanjiri loop'ni qanday "och qoldirishini" ko'rsatasiz va undan qochasiz.
  • Intervyudagi "bu kod nimani qaysi tartibda chiqaradi?" savoliga Node'da ham, brauzerda ham to'g'ri javob berasiz.

Oldin bilishingiz kerak: Event loop fazalari chuqur, Microtask va macrotask chuqur, Callbacklar va callback hell ("Zalgo").

1. Nega bu kerak?

Microtask va macrotask chuqur darsida bitta va'da qoldirgan edik: "process.nextTick .cjs faylda Promise microtask'laridan oldin ishlaydi, .mjs faylning yuqori darajasida — keyin. Sababi — 24-qismda". Bugun shu qarzni yopamiz.

Bu farq amalda muhim. Ko'p maqolalarda "nextTick doim Promise'dan oldin" deyilgan — ular CommonJS davrida yozilgan. Yangi Node loyihalari esa ko'pincha ESM'da, valyuta ham.

Mana intervyuning sevimli savoli. Javobni o'ylab ko'ring:

js
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
process.nextTick(() => console.log("4"));
(async () => {
  console.log("5");
  await null;
  console.log("6");
})();
console.log("7");

To'g'ri javob — "qaysi faylda?". .cjs da bitta tartib chiqadi, .mjs da boshqasi. Ikkala javobni dars oxiridagi «Intervyu savollari» bo'limida haqiqiy chiqish bilan ko'ramiz.

«Bahor» oshxonasida: oshpaz (V8) bitta buyurtmani tugatgach, keyingi stansiyaga o'tishdan oldin ikki ilgichga qaraydi — "shu zahoti" (nextTick) va "buyurtma tugashi bilan" (microtask). Ikkalasi bo'shamaguncha u joyidan qo'zg'almaydi.

2. Node'ning ikki "zudlik" navbati

2.1 Qoida

Node'da fazalardan tashqari yana ikki navbat bor:

  1. nextTick navbati (next tick queue) — process.nextTick(fn) qo'ygan callback'lar. Bu navbat V8'da emas, Node'ning o'zida.
  2. Microtask navbati (microtask queue) — Promise reaksiyalari (then, catch, finally, await davomi) va queueMicrotask. Bu navbat V8'niki — brauzerdagi bilan bir xil.

Qoida: har bir JavaScript callback'i tugagach (taymer, setImmediate, I/O callback'i, skriptning o'zi), Node:

  1. nextTick navbatini to'liq bo'shatadi;
  2. keyin microtask navbatini to'liq bo'shatadi;
  3. bu vaqtda nextTick navbatiga yangi ish tushgan bo'lsa — 1-qadamga qaytadi;
  4. ikkalasi ham bo'sh bo'lgachgina event loop davom etadi.
flowchart TB
  CB["Callback tugadi"] --> NT["nextTick navbati: to'liq"]
  NT --> MT["Microtask navbati: to'liq"]
  MT --> Q{"nextTick'da yangi ish bormi?"}
  Q -->|ha| NT
  Q -->|yo'q| L["Event loop: keyingi callback yoki faza"]

Nimaga qarang: nextTick va microtask'lar fazalarning ichida emas, har callback'dan keyin. Shuning uchun ular oldingi darsdagi doiraning birorta fazasiga ham kirmaydi.

2.2 CommonJS faylda: nextTick birinchi

Birinchi tajriba. Bu blok .cjs fayl sifatida ishga tushirilgan. .cjs — Node'ning eski modul tizimi CommonJS fayli; uni CommonJS ichidan darsida o'rganamiz, hozir faqat kengaytmasi muhim. O'zingizda tartib.cjs deb saqlang va node tartib.cjs bilan ishga tushiring:

js
const yoz = (x) => console.log(x);
setTimeout(() => yoz("setTimeout 0"), 0);
process.nextTick(() => yoz("nextTick"));
Promise.resolve().then(() => yoz("Promise.then"));
queueMicrotask(() => yoz("queueMicrotask"));
yoz("sinxron");

Konsolda:

text
sinxron
nextTick
Promise.then
queueMicrotask
setTimeout 0

Qoida bo'yicha: skript (birinchi callback) tugadi → nextTick navbati (nextTick) → microtask navbati (Promise.then, queueMicrotask — qo'yilgan tartibda) → event loop timers fazasiga keldi (setTimeout 0). setImmediate ni ataylab qo'shmadik — oldingi darsdan bilasiz, yuqori darajada uning taymer bilan tartibi kafolatlanmagan.

2.3 Har callback'dan keyin — faza oxirida emas

Ikki setImmediate bitta check fazasida turibdi. Ularning har biri o'z microtask'ini qo'yadi. Microtask'lar faza oxirida bajariladimi yoki har callback'dan keyin?

js
setImmediate(() => {
  console.log("immediate 1");
  Promise.resolve().then(() => console.log("promise 1"));
  process.nextTick(() => console.log("tick 1"));
});
setImmediate(() => {
  console.log("immediate 2");
  Promise.resolve().then(() => console.log("promise 2"));
});

Konsolda:

text
immediate 1
tick 1
promise 1
immediate 2
promise 2

Har callback'dan keyin. immediate 2 ga navbat kelishidan oldin tick 1 va promise 1 bajarildi — fazada boshqa callback kutib turgan bo'lsa ham. Node 11 dan (2018) beri shunday; eski maqolalarda boshqa javob uchrashi mumkin.

Brauzer ham xuddi shu qoidaga amal qiladi. setImmediate o'rniga taymerlar bilan — bu blok saytda ham, Node'da ham bir xil:

js
setTimeout(() => {
  console.log("taymer 1");
  Promise.resolve().then(() => console.log("promise 1"));
}, 0);
setTimeout(() => {
  console.log("taymer 2");
  Promise.resolve().then(() => console.log("promise 2"));
}, 0);

Konsolda:

text
taymer 1
promise 1
taymer 2
promise 2

2.4 Qadamma-qadam

Endi uchta navbatni bitta kodda kuzatamiz. Kod I/O callback ichida — u yerda .cjs va .mjs farqi yo'q (nega — keyingi bo'limda):

Tekshirib ko'ring: setImmediate callback'ining ichida process.nextTick qo'yildi. U keyingi aylanishda ishlaydimi?

Javob

Yo'q, darhol — o'sha setImmediate callback'i tugashi bilan, keyingi callback'dan ham oldin. nextTick navbati har callback'dan keyin bo'shatiladi, aylanishni kutmaydi. Shuning uchun "next tick" nomi chalg'itadi: u "keyingi aylanish" emas, "joriy ishdan keyin darhol".

3. ESM syurprizi

3.1 Xuddi shu kod .mjs da

Endi aynan o'sha kodni ESM fayl sifatida ishga tushiramiz (tartib.mjs, node tartib.mjs):

js
const yoz = (x) => console.log(x);
setTimeout(() => yoz("setTimeout 0"), 0);
process.nextTick(() => yoz("nextTick"));
Promise.resolve().then(() => yoz("Promise.then"));
queueMicrotask(() => yoz("queueMicrotask"));
yoz("sinxron");

Konsolda:

text
sinxron
Promise.then
queueMicrotask
nextTick
setTimeout 0

nextTick ikkinchidan to'rtinchi o'ringa tushdi. Biz ikkala faylni 30 martadan ishga tushirdik — natija har safar bir xil. Bu tasodif emas, qoida.

3.2 Nega shunday?

Node hujjati (process.nextTick bo'limi, 2026-10-23 da tekshirildi) buni ochiq yozadi. Mazmuni:

  • nextTick navbati bo'shatilgan har safar, darhol keyin microtask navbati ham bo'shatiladi — CJS'da shuning uchun nextTick birinchi.
  • ESM modullar esa allaqachon microtask navbatining bir qismi sifatida bajariladi. Node microtask'larni bo'shatish jarayonining ichida turibdi. Shuning uchun ESM'da queueMicrotask callback'lari doim nextTick dan oldin.

Oddiyroq qilib aytganda. ESM modulni Node asinxron yuklaydi: avval import'lar topiladi, keyin modul Promise ichida bajariladi. Modulning yuqori darajasidagi kodingiz — aslida bitta microtask. U tugaganda V8 microtask navbatini bo'shatishni davom ettiradi: siz qo'ygan then va queueMicrotask ham shu navbatda. Node'ning nextTick navbatiga esa navbat microtask'lar tugagandagina keladi.

Oshxonada: modulingiz — oshpaz bajarayotgan "buyurtma tugashi bilan" qog'ozlaridan biri. Siz qo'shgan yangi qog'ozlar shu ilgichning oxiriga tushadi va oshpaz uni bo'shatmaguncha "shu zahoti" ilgichiga qaramaydi.

3.3 Farq faqat modulning yuqori darajasida

ESM faylda ham callback ichiga kirsangiz, CJS qoidasi qaytadi:

js
setTimeout(() => {
  process.nextTick(() => console.log("nextTick"));
  Promise.resolve().then(() => console.log("Promise.then"));
  console.log("taymer ichida");
}, 0);

Konsolda:

text
taymer ichida
nextTick
Promise.then

Taymer callback'i event loop'dan keladi — microtask ichida emas. U tugagach Node odatdagi qoida bilan avval nextTick, keyin microtask navbatini bo'shatadi.

Top-level await dan keyin-chi (Dinamik import va top-level await)?

js
await null;
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(() => console.log("Promise.then"));
console.log("await'dan keyin");

Konsolda:

text
await'dan keyin
Promise.then
nextTick

Hali ham Promise birinchi. await dan keyingi kod — modulning davomi, u ham microtask sifatida ishlaydi. Xulosa: ESM modulning yuqori darajasida (await'dan oldin ham, keyin ham) microtask'lar nextTick dan oldin. Har qanday taymer, I/O yoki setImmediate callback'i ichida — nextTick birinchi.

3.4 Ichma-ich: navbat ichidan navbatga

Endi qiyinroq holat. nextTick ichida Promise, Promise ichida nextTick qo'yamiz. Avval .cjs:

js
const log = (x) => console.log(x);
process.nextTick(() => {
  log("tick 1");
  Promise.resolve().then(() => log("promise tick 1 ichidan"));
});
process.nextTick(() => log("tick 2"));
Promise.resolve().then(() => {
  log("promise 1");
  process.nextTick(() => log("tick promise 1 ichidan"));
});
Promise.resolve().then(() => log("promise 2"));

Konsolda:

text
tick 1
tick 2
promise 1
promise 2
promise tick 1 ichidan
tick promise 1 ichidan

Qadamma-qadam: nextTick navbati to'liq (tick 1, tick 2). Keyin microtask navbati to'liq: unda promise 1, promise 2 va tick 1 qo'shgan Promise bor — hammasi bajariladi. promise 1 qo'ygan nextTick esa microtask'lar tugashini kutadi va eng oxirida chiqadi.

Xuddi shu kod .mjs da:

js
const log = (x) => console.log(x);
process.nextTick(() => {
  log("tick 1");
  Promise.resolve().then(() => log("promise tick 1 ichidan"));
});
process.nextTick(() => log("tick 2"));
Promise.resolve().then(() => {
  log("promise 1");
  process.nextTick(() => log("tick promise 1 ichidan"));
});
Promise.resolve().then(() => log("promise 2"));

Konsolda:

text
promise 1
promise 2
tick 1
tick 2
tick promise 1 ichidan
promise tick 1 ichidan

Jadvalda qadamma-qadam (p — Promise, t — nextTick):

Qadam Nima bajariladi Konsolda
1 Modul kodi tugadi. Navbatlar: nextTick — t1, t2; microtask — p1, p2 —
2 Microtask navbati (biz hali modul ichidamiz). p1 nextTick navbatiga yangi tick qo'shadi promise 1, promise 2
3 nextTick navbati to'liq: t1, t2 va p1 qo'shgan tick. t1 microtask navbatiga yangi Promise qo'shadi tick 1, tick 2, tick promise 1 ichidan
4 Microtask navbati yana: t1 qo'shgan Promise promise tick 1 ichidan

Bu yerda microtask'lar birinchi (modul ichidamiz). Keyin nextTick navbati to'liq — unda uchta ish: tick 1, tick 2 va promise 1 qo'shgan tick. Oxirida tick 1 qo'shgan Promise. Ikki tartib butunlay boshqa — kod esa bir xil.

Tekshirib ko'ring: .mjs faylning yuqori darajasida process.nextTick callback'ini Promise'dan oldin ishlatish kerak. Kodni qanday o'zgartirasiz?

Javob

Ikkalasini biror callback ichiga qo'ying — masalan setTimeout(() => { ... }, 0) yoki setImmediate(() => { ... }). Callback event loop'dan keladi va undan keyin CJS qoidasi ishlaydi. Lekin yaxshiroq yo'l — tartibga bunchalik bog'liq kod yozmaslik: ikkita ish ketma-ket bo'lishi kerak bo'lsa, ikkinchisini birinchisining ichidan chaqiring.

4. Uchta "keyinroq" — bitta jadvalda

Oldingi darsdagi setImmediate va setTimeout(0) ni bugungi navbatlar bilan birlashtiramiz:

API Navbat Qachon ishlaydi Brauzerda
process.nextTick(fn) nextTick (Node) joriy callback'dan keyin darhol yo'q
queueMicrotask(fn), then microtask (V8) nextTick'dan keyin (ESM yuqorisida — oldin) bor
setImmediate(fn) check fazasi shu yoki keyingi aylanishning check'ida yo'q
setTimeout(fn, 0) timers fazasi kamida 1 ms dan keyin, timers'da bor (0 ms)

Nomlar teskari tuyuladi: nextTick "keyingi aylanish" degani emas, u eng tezi; setImmediate esa "darhol" emas, u eng sekinlaridan. Node'ning o'z qo'llanmasida ham "nomlarni almashtirish kerak edi, lekin endi kech — ko'p paketlar buzilib ketadi" deyilgan.

5. Ochlik (starvation)

5.1 nextTick zanjiri taymerni kutdiradi

Microtask va macrotask chuqur darsida microtask ochligini ko'rgan edingiz. nextTick bilan ham xuddi shunday — navbat bo'shamaguncha loop qimirlamaydi:

js
let n = 0;
function yana() {
  n++;
  console.log("nextTick", n);
  if (n < 4) process.nextTick(yana);
}
setTimeout(() => console.log("taymer"), 0);
process.nextTick(yana);

Konsolda:

text
nextTick 1
nextTick 2
nextTick 3
nextTick 4
taymer

Har yana() yangi nextTick qo'shadi va navbat hech bo'shamaydi — to'rtta chegara qo'yganimiz uchungina taymerga navbat keldi. Chegara bo'lmasa, taymer hech qachon ishlamaydi. Fayl o'qish, tarmoq javobi, server so'rovlari ham — hammasi kutadi.

Qancha kutishini o'lchadik: zanjir 20 ms davom etadigan qilib yozdik (har tick vaqtni tekshiradi). Natija vaqtga bog'liq, shuning uchun bu blok tekshirilmaydi:

js
const boshi = Date.now();
let n = 0;
setTimeout(() => {
  const ms = Date.now() - boshi;
  console.log(`taymer ${ms} ms da, ${n} ta tick'dan keyin`);
}, 0);
function yana() {
  n++;
  if (Date.now() - boshi < 20) process.nextTick(yana);
}
process.nextTick(yana);

Biz uch marta ishga tushirdik (Node 24.21.0, Windows 11, i5-12500H):

text
taymer 20 ms da, 13039 ta tick'dan keyin
taymer 20 ms da, 11859 ta tick'dan keyin
taymer 20 ms da, 3971 ta tick'dan keyin

0 ms li taymer roppa-rosa 20 ms kutdi — zanjir tugashini. Tick'lar soni har safar boshqa, lekin "20 ms" o'zgarmadi.

5.2 Promise zanjiri nextTick'ni ham kutdiradi

Navbatlar bir-birini ham och qoldiradi. ESM modulida microtask'lar birinchi ekanini bilasiz. Promise zanjiri nextTick ni kutdiradi:

js
let n = 0;
function yana() {
  n++;
  console.log("Promise", n);
  if (n < 3) Promise.resolve().then(yana);
}
process.nextTick(() => console.log("nextTick"));
Promise.resolve().then(yana);

Konsolda:

text
Promise 1
Promise 2
Promise 3
nextTick

5.3 setImmediate zanjiri ochlik bermaydi

setImmediate ichida yana setImmediate qo'ysangiz, yangisi keyingi aylanishning check fazasiga tushadi. Orada timers va poll ham o'z navbatini oladi. 20 ms davom etadigan setImmediate zanjiri va 5 ms li taymer:

js
let n = 0;
const boshi = Date.now();
setTimeout(() => console.log(`taymer: ${n}-aylanishda`), 5);
function yana() {
  n++;
  if (Date.now() - boshi < 20) setImmediate(yana);
}
setImmediate(yana);

Biz uch marta ishga tushirdik:

text
taymer: 511-aylanishda
taymer: 565-aylanishda
taymer: 589-aylanishda

Taymer o'z vaqtida — zanjir hali davom etayotganda — ishladi. Zanjir har aylanishda loop'ga "nafas" beradi. Uzun ishni bo'laklashda aynan shu xususiyatdan foydalanamiz (Event loop'ni bloklash).

6. Qaysi birini tanlash kerak

6.1 Sukut: queueMicrotask

Node hujjatining tavsiyasi: ko'p hollarda process.nextTick o'rniga queueMicrotask ishlating. Sabablari:

  • Brauzerda ham, Node'da ham, Deno va Bun'da ham bir xil ishlaydi.
  • Tartibi Promise bilan bir navbatda — CJS/ESM farqi uni chalkashtirmaydi.

6.2 process.nextTick qayerda uchraydi

nextTick eski emas va taqiqlanmagan. U Node'ning o'z kutubxonalarida va ko'p eski paketlarda bor. Ikki odatiy vazifasi:

  1. Callback'ni doim asinxron qilish. Callbacklar va callback hell darsidagi "Zalgo"ni eslang: funksiya ba'zan callback'ni darhol, ba'zan keyin chaqirsa, uni ishlatgan kod chalkashadi. Javob keshda bo'lsa ham nextTick bilan keyinroq chaqirish — klassik yechim.
  2. Hodisani konstruktordan keyin chiqarish. Obyekt yaratilayotganda hodisa chiqarilsa, tinglovchi hali ulanmagan bo'ladi. nextTick hodisani "ulanishdan keyin"ga suradi — buni EventEmitter asoslari darsida ko'ramiz.

nextTick ning bitta qulayligi bor: u callback'ga argumentlarni ham uzatadi — process.nextTick(fn, a, b). queueMicrotask bunday qila olmaydi. Birinchi vazifa misolida:

js
const kesh = new Map();

function narxniOl(taom, callback) {
  if (kesh.has(taom)) {
    process.nextTick(callback, null, kesh.get(taom));
    return;
  }
  setTimeout(() => {
    kesh.set(taom, 35000);
    callback(null, 35000);
  }, 10);
}

narxniOl("osh", (xato, narx) => {
  console.log("1-chaqiruv:", narx);
  narxniOl("osh", (xato2, narx2) => {
    console.log("2-chaqiruv:", narx2);
  });
  console.log("2-so'rov yuborildi");
});

Konsolda:

text
1-chaqiruv: 35000
2-so'rov yuborildi
2-chaqiruv: 35000

Ikkinchi chaqiruvda narx keshdan olindi, lekin callback baribir "so'rov yuborildi" dan keyin ishladi. Chaqiruvchi kod uchun kesh bor-yo'qligi farq qilmaydi — doim asinxron. kesh.has tarmog'ida callback(null, kesh.get(taom)) ni to'g'ridan chaqirsangiz, 2-chaqiruv qatori 2-so'rov yuborildi dan oldin chiqadi.

6.3 setImmediate — loop'ga nafas berish

Og'ir ishni bo'laklarga bo'lib, orasida loop'ga I/O va taymerlarni bajarishga imkon berish kerak bo'lsa — setImmediate. nextTick yoki microtask bu ish uchun yaramaydi: ochlik bo'limida ko'rdingiz, ular loop'ni qo'yib yubormaydi.

Vazifa Tanlov
"Joriy kod tugagach, boshqa hech narsadan oldin" queueMicrotask
Callback'ni doim asinxron chaqirish (callback API) process.nextTick
Uzun ishni bo'laklash, I/O'ga yo'l berish setImmediate
Aniq vaqtdan keyin setTimeout

7. Brauzer bilan farq

Brauzer Node
Microtask navbati bor bor (o'sha V8 navbati)
process.nextTick yo'q bor, microtask'dan alohida
setImmediate yo'q check fazasi
.cjs/.mjs farqi yo'q nextTick tartibiga ta'sir qiladi

Umumiy qism — Promise, async/await, queueMicrotask, setTimeout. Intervyu savolidagi nextTick qatorini olib tashlasak, kod ikkala muhitda bir xil natija beradi. Saytda «Ishga tushir» bilan sinang:

js
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
(async () => {
  console.log("5");
  await null;
  console.log("6");
})();
console.log("7");

Konsolda:

text
1
5
7
3
6
2

async funksiya await gacha sinxron ishlaydi (5), await dan keyingi qism microtask (6) — then (3) dan keyin, chunki u keyinroq navbatga tushdi.

8. Intervyu savollari

8.1 Dars boshidagi savol

.cjs faylda (node savol.cjs):

js
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
process.nextTick(() => console.log("4"));
(async () => {
  console.log("5");
  await null;
  console.log("6");
})();
console.log("7");

Konsolda:

text
1
5
7
4
3
6
2

.mjs faylda (node savol.mjs):

js
console.log("1");
setTimeout(() => console.log("2"), 0);
Promise.resolve().then(() => console.log("3"));
process.nextTick(() => console.log("4"));
(async () => {
  console.log("5");
  await null;
  console.log("6");
})();
console.log("7");

Konsolda:

text
1
5
7
3
6
4
2

Yaxshi javob: "Sinxron qism — 1 5 7. Keyin fayl turiga bog'liq: CommonJS'da nextTick microtask'lardan oldin, ESM modulida — keyin. Taymer 2 doim oxirida".

8.2 Boshqa savollar

  • "process.nextTick va setImmediate farqi?" — nextTick joriy callback'dan keyin darhol, fazaga kirmaydi; setImmediate — check fazasida.
  • "nextTick ni rekursiv chaqirsa?" — loop och qoladi: taymer va I/O kutadi.

9. CLI qadami: valyuta va navbatlar

Bu darsda valyuta ga kod qo'shilmaydi va commit yo'q — loyiha process obyekti darsida boshlanadi.

Yakuniy kanon kodida (19 ta manba fayl va testlar) birorta ham process.nextTick, queueMicrotask yoki setImmediate yo'q — qidirib tekshirdik. CLI async/await bilan yozilgan: microtask'lar bor, lekin ular await ichida yashirin, qo'lda navbat boshqarish kerak bo'lmadi. Bitta buyruq, bitta foydalanuvchi — ishni bo'laklash yoki "keyinga surish" ehtiyoji yo'q.

Bitta fakt baribir foydali. valyuta ning kirish fayli ESM va unda top-level await bor (process.exitCode = await main(...)). Demak u ishlaganda bugungi "ESM yuqori darajasi" qoidasi amal qiladi. Kodga process.nextTick qo'shsangiz, u maqolalardagidek "eng birinchi" emas, Promise'lardan keyin ishlaydi.

Kanon tajribasi (t03-tartib, Node 24.21.0) ham shuni ko'rsatdi: ESM modul darajasida sinxron → Promise.then → queueMicrotask → nextTick → setTimeout 0, I/O callback ichida esa nextTick → Promise.then → setImmediate → setTimeout 0.

10. Ko'p uchraydigan xatolar

10.1 "nextTick doim Promise'dan oldin"

Faqat CommonJS'da va callback ichida to'g'ri. ESM modulining yuqori darajasida — teskari. Maqoladagi tartib misolini o'qiyotganda, avval fayl turiga qarang: require bormi yoki import?

10.2 nextTick bilan "loop'ga nafas berish"

Uzun hisobni bo'laklab, har bo'lakdan keyin process.nextTick(davomi) qilish — foydasiz: nextTick navbati bo'shamaguncha loop baribir to'xtab turadi. Bo'laklash uchun — setImmediate.

10.3 Rekursiv nextTick yoki Promise

Chegarasiz zanjir dasturni qotiradi: CPU band, lekin so'rovlarga javob yo'q. Xato xabari ham chiqmaydi — shuning uchun topish qiyin. Rekursiyada chegara qo'ying yoki setImmediate ga o'ting.

10.4 Node tartibini brauzerga ko'chirish

Intervyu yechimini brauzer konsolida tekshirmoqchi bo'lsangiz:

text
Uncaught ReferenceError: process is not defined

Tarjimasi: "ushlanmagan xato: process aniqlanmagan". Brauzerda process ham, setImmediate ham yo'q. Brauzer uchun misollarni faqat Promise, queueMicrotask va setTimeout bilan yozing.

10.5 .js fayl qaysi rejimda?

.js fayl package.json dagi "type" maydoniga qarab CommonJS yoki ESM bo'ladi. Bir xil tartib.js ikki loyihada ikki xil tartib berishi mumkin. Tartib tajribalari uchun aniq kengaytma ishlating: .cjs yoki .mjs. "type" ni ES Modules Node'da darsida ko'ramiz.

11. Mashqlar

Mashqlar kurs/mashqlar/24/04-microtask-nexttick/ da.

1-mashq (oson): bo'sh joylarni to'ldiring

  • then va queueMicrotask callback'lari tushadigan navbat — .
  • .cjs faylda yuqori darajada birinchi ishlaydigan navbat — .
  • Brauzerda yo'q, check fazasida ishlaydigan funksiya — .
  • Node hujjati sukut bo'yicha tavsiya qiladigan funksiya — .
Yechim

microtask — V8 navbati, Promise reaksiyalari va queueMicrotask. nextTick — CJS'da callback'dan keyin birinchi bo'shatiladi. setImmediate — check fazasi, faqat Node. queueMicrotask — hamma muhitda bir xil va CJS/ESM farqiga bog'liq emas.

2-mashq (o'rta): ikki fayl, ikki javob

Shu kod .cjs va .mjs faylda nima chiqaradi? Ikkala javobni ham ishga tushirmasdan yozing, keyin tekshiring.

js
process.nextTick(() => console.log("A"));
Promise.resolve().then(() => {
  console.log("B");
  process.nextTick(() => console.log("C"));
});
setImmediate(() => console.log("D"));
console.log("E");
Yechim

.cjs:

js
process.nextTick(() => console.log("A"));
Promise.resolve().then(() => {
  console.log("B");
  process.nextTick(() => console.log("C"));
});
setImmediate(() => console.log("D"));
console.log("E");

Konsolda:

text
E
A
B
C
D

.mjs:

js
process.nextTick(() => console.log("A"));
Promise.resolve().then(() => {
  console.log("B");
  process.nextTick(() => console.log("C"));
});
setImmediate(() => console.log("D"));
console.log("E");

Konsolda:

text
E
B
A
C
D

E — sinxron, ikkalasida birinchi. CJS'da: nextTick navbati (A), microtask (B, u C ni qo'shadi), yana nextTick (C), keyin check (D). ESM'da: microtask birinchi (B — C ni nextTick navbatiga A ning orqasiga qo'yadi), keyin nextTick navbati to'liq (A, C), keyin check (D). Bu yerda setImmediate yuqori darajada bo'lsa ham xavfsiz: yonida taymer yo'q, u doim eng oxirida.

3-mashq (qiyin): Zalgo'ni tuzating

matnniOl(callback) faylni bir marta o'qiydi va keshlaydi. sinov() chaqiruvdan keyin tayyor = true qiladi va callback "tayyor" holatini chiqaradi. Kodni ishga tushiring: ikkinchi chaqiruv nega false chiqaradi? Keyin matnniOl ni shunday tuzatingki, ikkala chaqiruv ham true chiqarsin. Ishora: process.nextTick(fn, arg) argumentni ham uzatadi — ushbu darsdagi «Qaysi birini tanlash kerak» bo'limiga qarang.

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

let kesh = null;
function matnniOl(callback) {
  if (kesh !== null) {
    callback(kesh);
    return;
  }
  readFile(import.meta.filename, "utf8", (xato, matn) => {
    kesh = matn;
    callback(matn);
  });
}

function sinov(nomi) {
  let tayyor = false;
  matnniOl(() => console.log(nomi, "tayyor:", tayyor));
  tayyor = true;
}

sinov("1-chaqiruv");
setTimeout(() => sinov("2-chaqiruv"), 50);

Konsolda:

text
1-chaqiruv tayyor: true
2-chaqiruv tayyor: false
Yechim

Birinchi chaqiruvda fayl o'qiladi — callback keyin ishlaydi, tayyor allaqachon true. Ikkinchisida matn keshda — callback darhol, tayyor = true qatoridan oldin chaqiriladi. Bitta funksiya ikki xil xulq ko'rsatdi — bu Zalgo. Tuzatish — kesh tarmog'ida ham callback'ni keyinga surish:

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

let kesh = null;
function matnniOl(callback) {
  if (kesh !== null) {
    process.nextTick(callback, kesh);
    return;
  }
  readFile(import.meta.filename, "utf8", (xato, matn) => {
    kesh = matn;
    callback(matn);
  });
}

function sinov(nomi) {
  let tayyor = false;
  matnniOl(() => console.log(nomi, "tayyor:", tayyor));
  tayyor = true;
}

sinov("1-chaqiruv");
setTimeout(() => sinov("2-chaqiruv"), 50);

Konsolda:

text
1-chaqiruv tayyor: true
2-chaqiruv tayyor: true

queueMicrotask(() => callback(kesh)) ham ishlaydi — u ham tayyor = true dan keyin bajariladi. Ikkinchi chaqiruv taymer callback'i ichida bo'lgani uchun ikkalasi ham shu callback tugagach darhol ishlaydi. Yangi kodda esa Promise qaytaruvchi funksiya (fs/promises) yaxshiroq: await doim asinxron.

4-mashq: CLI qadami — ESM va CJS'ni o'zingizda solishtiring

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

  1. «CommonJS faylda: nextTick birinchi» bo'limidagi kodni tartib.cjs va tartib.mjs ga saqlang. Ikkalasini 5 martadan ishga tushiring. Tartib o'zgaradimi?
  2. tartib.mjs dagi butun kodni setTimeout(() => { ... }, 0) ichiga o'rang. nextTick qayerga ko'chdi?
  3. node --version ni yozib qo'ying va natijangizni darsdagi bilan solishtiring.
Yechim
  1. Har fayl o'z ichida barqaror: .cjs — sinxron, nextTick, Promise.then, queueMicrotask, setTimeout 0; .mjs — sinxron, Promise.then, queueMicrotask, nextTick, setTimeout 0. Biz har birini 30 martadan ishga tushirdik — bir xil.
  2. O'ralgan .mjs CJS tartibini beradi — nextTick yana sinxron dan keyin birinchi. Kod endi modul darajasida emas, taymer callback'ida; callback tugagach Node odatdagidek avval nextTick navbatini bo'shatadi. Ichki setTimeout 0 esa keyingi aylanishda.
  3. Darslar Node 24.21.0 da tekshirilgan. Bu tartib Node hujjatida yozilgan xulq; boshqa versiyada farq chiqsa, o'sha versiya hujjatining process.nextTick bo'limini o'qing.

12. Real ishda

  • Intervyu: "Bu kod qaysi tartibda chiqaradi?" — Node intervyusida deyarli doim nextTick bilan keladi.
  • Kutubxona kodi: Node'ning stream va events modullari, eski callback API'li paketlar ichida process.nextTick ko'p.
  • Osilib qolgan server: CPU 100 %, lekin so'rovlarga javob yo'q, xato logi bo'sh — sabablaridan biri cheksiz nextTick yoki Promise zanjiri.
  • ESM'ga ko'chish: CommonJS loyihani ESM'ga o'tkazganda, yuqori darajadagi nextTick va Promise tartibiga tayangan kod (masalan modul yuklanishidagi ishga tushirish) o'zgacha ishlay boshlaydi. Ko'chish testlarida buni tekshiring.

Xulosa

  • Har JavaScript callback'idan keyin Node avval nextTick navbatini, keyin microtask navbatini to'liq bo'shatadi — shundan keyin event loop davom etadi.
  • CommonJS faylda va har qanday callback ichida nextTick Promise'dan oldin; ESM modulining yuqori darajasida — keyin, chunki modul o'zi microtask ichida bajariladi.
  • setImmediate — check fazasi, setTimeout(0) — timers fazasi; ikkalasi ham microtask va nextTick'dan keyin.
  • nextTick va Promise zanjiri loop'ni och qoldiradi (biz 0 ms li taymerni 20 ms kutdirdik); setImmediate zanjiri — yo'q.
  • Sukut tanlov — queueMicrotask; nextTick — callback'ni doim asinxron qilish uchun; setImmediate — ishni bo'laklash uchun.

Keyingi dars: libuv thread pool va OS asinxron I/O — fayl, crypto va dns qaysi thread'larda bajariladi va 4 ta ishchi thread qachon tor joyga aylanadi.

Manbalar

Ulashish:Telegram'da

Izohlar (0)

Izoh yozish uchun kiring.

  • Hozircha izoh yo'q. Birinchi bo'ling!
process.nextTick va microtask navbati Node'da: nextTick, queueMicrotask va setImmediate tartibi — IlmHamroh