Series: JavaScript Basics Lesson 49

Microtask vs Macrotask

Macrotask va Microtask nega ikki xil navbat ekani, ularning to'liq manbalar ro'yxati va farqlar jadvali, queueMicrotask, Node.js'dagi process.nextTick, microtask starvation xavfi, va ikkalasi V8/runtime darajasida qanday farqlanishi.

49-dars

Microtask vs Macrotask

Avval — nega umuman ikkita xil "navbat" kerak?

Bitta savol tug'iladi: nega barcha asinxron ishlarni bitta Queue'ga solib qo'ymaslik kerak edi? Javob — ular tabiatan boshqa-boshqa maqsadga xizmat qiladi:

Macrotask — "tashqi dunyo"dan keladigan hodisalar: timer tugadi, foydalanuvchi tugma bosdi, tarmoq javob berdi. Bular mustaqil, bir-biriga bog'liq bo'lmagan voqealar.

Microtask — joriy vazifani "yakunlash" uchun kerak bo'lgan ish: Promise natijasini qayta ishlash, xatoni ushlash. Bu — davom ettirish (continuation), yangi mustaqil voqea emas.

Shuning uchun spesifikatsiya qat'iy talab qiladi: joriy "vazifa" to'liq o'z oqibatlari (Promise reaksiyalari) bilan birga tugamaguncha, keyingi tashqi voqeaga o'tilmaydi.


1. Ikkalasining to'liq ro'yxati

Macrotask (Task) manbalari:

  • setTimeout / setInterval
  • setImmediate (faqat Node.js)
  • DOM event handler (click, scroll, keypress...)
  • I/O operatsiyalar (fayl o'qish, tarmoq — Node.js'da)
  • MessageChannel / postMessage
  • Boshlang'ich <script> kodning o'zi (birinchi Macrotask)

Microtask manbalari:

  • Promise.then / .catch / .finally
  • async/await (ichida .then'ga aylanadi)
  • queueMicrotask(fn)
  • MutationObserver (DOM o'zgarishlarini kuzatish)
  • process.nextTick (Node.js — bu Microtask'dan HAM ustuvor, alohida)

2. Asosiy farq — jadval

Xususiyat Macrotask Microtask
Bir aylanishda nechtasi bajariladi Faqat bitta Hammasi, Queue bo'shaguncha
Prioritet Past Yuqori
Kim boshqaradi Runtime (brauzer/libuv) V8 Engine (spec darajasida)
Rendering bilan aloqasi Orasida render bo'lishi mumkin Render'dan oldin albatta tugaydi
Yangi qo'shilgani kutiladimi Yo'q (navbatga qo'yiladi, keyingi aylanishda) Ha — hatto ish jarayonida qo'shilsa ham

Eng muhim qator — birinchisi: Macrotask'dan faqat BITTASI olinadi, Microtask'dan esa HAMMASI. Aynan shu farq ko'p "g'alati" natijalarning sababi.


3. Amaliy misol — farqni "his qilish"

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

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

Natija:

micro 1
micro 2
macro 1
macro 2

Nega ikkalasi ham macro'lardan oldin? Chunki Call Stack bo'shagach, Event Loop birinchi navbatda Microtask Queue'ga qaraydi — u yerda ikkitasi bor, ikkalasini ham ketma-ket bajaradi (Queue to'liq bo'shaguncha). Faqat shundan keyin, Macrotask Queue'dan bittasini oladi (macro 1), so'ng yana Microtask Queue'ni tekshiradi (bo'sh), so'ng ikkinchi Macrotask'ni oladi (macro 2).


4. "Bitta olib, qayta tekshirish" — nega bu qoida muhim (real vaziyat)

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

setTimeout(() => {
  console.log('macro 3 (ichida micro yaratadi)');
  Promise.resolve().then(() => console.log('micro yaratilgan macro3 ichida'));
}, 0);

Bu yerda 3 ta macrotask ketma-ket navbatda. Har birini olib bajargandan keyin — Event Loop darhol ikkinchisiga o'tmaydi, avval Microtask Queue'ni tekshiradi:

macro 1
macro 2
macro 3 (ichida micro yaratadi)
micro yaratilgan macro3 ichida

Agar macro 3dan keyin yana bitta setTimeout bo'lganda edi, u faqat micro yaratilgan macro3 ichida bajarilib bo'lgandan keyin ishga tushardi — chunki har bir Macrotask bilan keyingi Macrotask orasida Microtask Queue albatta tekshiriladi.


5. queueMicrotask — Promise'siz microtask yaratish

Ba'zan Promise yaratmasdan, to'g'ridan-to'g'ri microtask qo'shish kerak bo'ladi:

console.log('1');

queueMicrotask(() => console.log('2 - microtask'));

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

console.log('4');

Natija: 1, 4, 2 - microtask, 3 - macrotask. queueMicrotask — Promise'ning "qobig'i"sisiz, to'g'ridan-to'g'ri Microtask Queue'ga funksiya qo'shadigan eng past darajadagi API. Aslida, Promise.resolve().then(fn) ichida ham, oxir-oqibat V8 aynan shu mexanizmni ishlatadi.


6. Node.js'da qo'shimcha qatlam — process.nextTick

Node.js'da yana bir, hatto Microtask'dan ham ustuvor navbat bor:

process.nextTick Queue   ← ENG YUQORI PRIORITET
Microtask Queue (Promise)
Macrotask Queue (setTimeout va h.k.)
setTimeout(() => console.log('macro'), 0);
Promise.resolve().then(() => console.log('micro'));
process.nextTick(() => console.log('nextTick'));

// Natija: nextTick, micro, macro

Bu — Node.js'ga xos, brauzerda process.nextTick umuman mavjud emas. Buni yodda tuting, aks holda brauzer va Node.js orasida kutilmagan farqlarga duch kelishingiz mumkin.


7. Xavfli holat — "Microtask Starvation"

Oldingi mavzuda qisman ko'rgan edik, endi to'liq misol bilan:

function loop() {
  Promise.resolve().then(loop); // o'zini-o'zi cheksiz chaqiradi
}
loop();

setTimeout(() => console.log('bu HECH QACHON chiqmaydi'), 0);

loop() har safar o'zini yangi microtask sifatida qayta rejalashtiradi. Event Loop qoidasiga ko'ra, Macrotask'ga o'tishdan oldin Microtask Queue to'liq bo'shashi kerak — lekin bu yerda u hech qachon bo'shamaydi (har safar bo'shashi bilan yangisi qo'shiladi). Natija: setTimeout abadiy kutadi, brauzerda esa rendering ham to'xtaydi — sahifa butunlay muzlaydi.

Bu — real loyihalarda rekursiv .then() zanjirlari yoki noto'g'ri yozilgan async funksiyalarda uchraydigan haqiqiy bug turi.


8. V8 darajasida — ikkalasi "jismonan" qayerda farqlanadi

┌───────────────────────────────────────────┐
│              V8 Engine                     │
│  ┌─────────────────────────────────────┐  │
│  │  Microtask Queue                     │  │
│  │  (spec: "PromiseJobs" / "Jobs Queue")│  │
│  │  — V8'ning O'ZI boshqaradi           │  │
│  │  — til (ECMAScript) darajasida       │  │
│  │    majburiy                          │  │
│  └─────────────────────────────────────┘  │
└───────────────────────────────────────────┘
              ▲
              │ V8 Call Stack bo'shaganda,
              │ IsolateHandleScope orqali
              │ chaqiriladi ("microtask checkpoint")
              │
┌───────────────────────────────────────────┐
│         Runtime (brauzer / libuv)          │
│  ┌─────────────────────────────────────┐  │
│  │  Macrotask (Task) Queue              │  │
│  │  — runtime boshqaradi                │  │
│  │  — V8 buning haqida "bilmaydi" ham   │  │
│  └─────────────────────────────────────┘  │
└───────────────────────────────────────────┘

Texnik atama: V8 ichida Call Stack bo'shagan har bir nuqtada "microtask checkpoint" (yoki "perform a microtask checkpoint") deb ataladigan tekshiruv chaqiriladi — bu funksiya Microtask Queue butunlay bo'shaguncha ishlaydi. Bu checkpoint — V8'ning o'z ichki API'si, uni brauzer ham, Node.js ham har bir Macrotask tugagach majburiy chaqiradi. Shuning uchun Microtask xatti-harakati barcha muhitlarda bir xil (brauzer, Node.js, Deno) — chunki bu V8'ning o'zida qattiq belgilangan, runtime'ga bog'liq emas. Macrotask esa har bir runtime'da boshqacha amalga oshirilishi mumkin.


Xulosa

Microtask = joriy ishning "davomi" (Promise natijasi) — Queue to'liq tozalanadi, V8'ning o'zi boshqaradi. Macrotask = tashqi, mustaqil voqea (timer, event) — bir aylanishda faqat bittasi, runtime boshqaradi. Har bir Macrotask tugagach, keyingisiga o'tishdan oldin, Microtask Queue albatta bo'shatiladi — shu tsikl abadiy takrorlanadi. Cheksiz microtask yaratish — Macrotask va rendering'ni butunlay to'xtatib qo'yishi mumkin.