Microtask va Macrotask — qaysi birinchi ishlaydi

~15 daq · Node.js · Event Loop

Muammodan boshlaylik

console.log('1');

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

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

console.log('4');

Natija nima? 1, 2, 3, 4? 1, 4, 2, 3? 1, 4, 3, 2?

Ko'pchilik noto'g'ri javob beradi. To'g'ri javob — 1, 4, 3, 2. Nima uchun setTimeout dan oldin Promise ishladi? Chunki ikkalasi turli navbatda turadi — biri macrotask, ikkinchisi microtask.


Macrotask nima?

Macrotask — "katta vazifa". Event Loop har bir aylanishida navbatdan bitta macrotask oladi va bajaradi.

Macrotask bo'ladigan narsalar:

setTimeout(() => {}, 0);        // macrotask
setInterval(() => {}, 100);     // macrotask (har 100ms)
setImmediate(() => {});         // macrotask (Node.js da)
fs.readFile('...', () => {});   // callback — macrotask
http.get('...', () => {});      // callback — macrotask

Macrotask navbati — oddiy navbat. Birinchi kelgan birinchi bajariladi. Har bir aylanishda faqat bitta bajariladi, keyin Event Loop keyingi ishga o'tadi.

Macrotask navbati:
┌──────────┬──────────┬──────────┐
│ callback1│ callback2│ callback3│
└──────────┴──────────┴──────────┘
     ↑
  Bitta olinadi, bajariladi

Microtask nima?

Microtask — "kichik, lekin shoshilinch vazifa". Macrotask dan farqli — navbatdagi barcha microtasklar bitta macrotask bajarilgandan keyin darhol bajariladi.

Microtask bo'ladigan narsalar:

Promise.resolve().then(() => {});   // microtask
Promise.reject().catch(() => {});   // microtask
process.nextTick(() => {});         // microtask (Node.js, eng yuqori)
queueMicrotask(() => {});           // microtask

Microtask navbati — yuqori ustuvorlikli navbat. Har qanday macrotask dan oldin to'liq bajariladi.

Microtask navbati:
┌──────────┬──────────┬──────────┐
│ nextTick1│ Promise1 │ Promise2 │
└──────────┴──────────┴──────────┘
     ↑
  Hammasi bajariladi — bitta ham qolmaydi

Qanday ishlaydi — to'liq rasm

Event Loop har bir aylanishida quyidagi tartibda ishlaydi:

┌─────────────────────────────────────────────────┐
│                                                 │
│  1. Sinxron kod bajariladi (Call Stack)         │
│                                                 │
│  2. process.nextTick navbati — to'liq bajariladi│
│                                                 │
│  3. Promise microtask navbati — to'liq bajariladi│
│                                                 │
│  4. Bitta macrotask bajariladi                  │
│     (setTimeout, setImmediate, I/O callback...) │
│                                                 │
│  5. Yana 2-qadamga qayt                         │
│                                                 │
└─────────────────────────────────────────────────┘

Muhim nuance: bitta macrotask bajarilgandan keyin — yana barcha microtasklar bajariladi. Keyin yana bitta macrotask. Va hokazo.


Qadam-baqadam misol

console.log('1');                                    // sinxron

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

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

console.log('4');                                    // sinxron

Nima bo'ladi:

Qadam 1: console.log('1') → "1" chiqadi

Qadam 2: setTimeout → macrotask navbatiga qo'yiladi
         (hali bajarilmaydi)

Qadam 3: Promise.then → microtask navbatiga qo'yiladi
         (hali bajarilmaydi)

Qadam 4: console.log('4') → "4" chiqadi

Qadam 5: Call Stack bo'shadi
         → microtask navbati tekshiriladi
         → Promise callback bor → "3" chiqadi
         → microtask navbati bo'shadi

Qadam 6: macrotask navbatidan bitta olinadi
         → setTimeout callback → "2" chiqadi

Natija:

1
4
3
2

Microtask ichida microtask

Microtask ichida yangi microtask qo'shilsa — u ham shu navbatda bajariladi. Macrotask ga o'tilmaydi.

Promise.resolve()
  .then(() => {
    console.log('1-Promise');
    return Promise.resolve(); // yangi microtask
  })
  .then(() => {
    console.log('2-Promise'); // bu ham microtask navbatida
  });

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

Natija:

1-Promise
2-Promise
setTimeout

setTimeout ikkala Promise ham tugamaguncha ishlamadi — chunki microtask navbati to'liq bo'shashi kerak.


process.nextTick — Promise dan ham oldin

Node.js da process.nextTick — alohida navbat. U Promise microtask navbatidan ham oldin ishlaydi.

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

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

Natija:

nextTick
Promise

Tartib:

1. process.nextTick navbati — to'liq bajariladi
2. Promise microtask navbati — to'liq bajariladi
3. Macrotask navbati — bitta bajariladi

Hammasi birgalikda

console.log('start');

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

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

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

console.log('end');

Natija:

start
end
nextTick 1
nextTick 2
Promise 1
Promise 2
setTimeout 1
setTimeout 2

Tartib:

1. Sinxron: start, end
2. nextTick navbati: nextTick 1, nextTick 2
3. Promise navbati: Promise 1, Promise 2
4. Macrotask: setTimeout 1
5. Microtask tekshiriladi (bo'sh)
6. Macrotask: setTimeout 2

setTimeout 1 va setTimeout 2 alohida-alohida bajariladi — har biridan keyin microtask navbati tekshiriladi.


Xulosa

Macrotask — setTimeout, setInterval, I/O callbacklar. Navbatdan bitta olinib bajariladi.

Microtask — Promise.then, process.nextTick. Har bir macrotask dan oldin navbat to'liq bajariladi.

Tartib:

sinxron kod
    ↓
process.nextTick (to'liq)
    ↓
Promise microtask (to'liq)
    ↓
bitta macrotask
    ↓
process.nextTick (to'liq)
    ↓
Promise microtask (to'liq)
    ↓
bitta macrotask
    ↓
...

Shu tartibni bilsangiz — istalgan asinxron kodning natijasini oldindan ayta olasiz.


Keyingi dars

11-dars: path va os — join, resolve, dirname, platform, cpus