Kita telah mengenal microtask dan fase-fase event loop secara terpisah; sekarang waktunya menyandingkan seluruh alat penundaan eksekusi dalam satu eksperimen. Node.js menyediakan process.nextTick(), setImmediate(), dan setTimeout(fn, 0) — tiga API yang sekilas tampak saling menggantikan, padahal ketiganya menempati lapisan penjadwalan yang berbeda. Chapter ini memetakan lapisan-lapisan tersebut lewat program yang urutan output-nya kita verifikasi langsung, termasuk satu kejutan pada level teratas module ESM yang sering membuat kode dari tutorial lama terlihat saling bertentangan.

Tiga Lapis Penjadwalan #

Pahami dulu di mana tiap API berada di dalam arsitektur runtime:

  • process.nextTick(callback) menaruh callback ke nextTick queue milik Node.js. Antrean ini dikosongkan tepat setelah callback yang sedang berjalan selesai — sebelum microtask queue, sebelum fase apa pun dari event loop berlanjut.
  • Promise.then(callback) dan queueMicrotask(callback) menempati microtask queue milik V8 — satu lapis di bawah nextTick queue.
  • setImmediate(callback) menaruh callback ke fase check yang dikunjungi setelah fase poll pada putaran event loop yang sama; setTimeout(callback, 0) menunggu fase timers pada putaran berikutnya.

Karena posisinya yang paling dekat dengan kode yang sedang berjalan, process.nextTick() adalah janji “segera setelah baris ini, sebelum apa pun yang lain”. Sementara setImmediate() adalah “segera setelah fase I/O selesai” — keduanya bukan “segera sekarang”, karena kode sinkron dan antrean yang lebih tinggi tetap didahulukan.

Urutan yang Terjamin di Dalam Callback I/O #

Eksperimen kunci chapter ini mendaftarkan keempat API sekaligus di dalam callback I/O — posisi di mana seluruh aturan prioritas berlaku penuh:

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

readFile(new URL("./urutan-io.mjs", import.meta.url), () => {
  setTimeout(() => console.log("4. setTimeout(0)   : fase timers"), 0);
  setImmediate(() => console.log("3. setImmediate    : fase check"));
  Promise.resolve().then(() => console.log("2. Promise.then    : microtask queue"));
  process.nextTick(() => console.log("1. process.nextTick: nextTick queue"));
});

Output yang Dihasilkan:

text
1. process.nextTick: nextTick queue
2. Promise.then    : microtask queue
3. setImmediate    : fase check
4. setTimeout(0)   : fase timers

Urutan tercetak berlawanan dengan urutan pendaftaran di kode, dan itulah yang terjamin:

  1. process.nextTick lebih dulu: begitu callback readFile selesai, nextTick queue dikosongkan sebelum microtask queue sempat tersentuh.
  2. Promise.then menyusul: microtask queue dikosongkan seluruhnya sebelum event loop melanjutkan putarannya.
  3. setImmediate berikutnya: eksekusi sedang berada di fase poll, sehingga fase check dikunjungi pada putaran yang sama.
  4. setTimeout(0) terakhir: fase timers baru dikunjungi lagi pada putaran berikutnya.

Urutan empat lapis ini stabil dan dapat diandalkan selama pendaftaran dilakukan dari dalam callback event loop — baik callback I/O, timer, maupun setImmediate itu sendiri.

Kejutan di Level Teratas Module ESM #

Situasi menjadi tidak terduga ketika keempat callback didaftarkan langsung di baris teratas module. Bandingkan dua program berikut — isinya identik, hanya berbeda format module:

js
process.nextTick(() => console.log("2. process.nextTick"));
Promise.resolve().then(() => console.log("1. Promise.then"));
setTimeout(() => console.log("3. setTimeout(0)"), 0);

Output yang Dihasilkan:

text
1. Promise.then
2. process.nextTick
3. setTimeout(0)
js
process.nextTick(() => console.log("1. process.nextTick"));
Promise.resolve().then(() => console.log("2. Promise.then"));
setTimeout(() => console.log("3. setTimeout(0)"), 0);

Output yang Dihasilkan:

text
1. process.nextTick
2. Promise.then
3. setTimeout(0)

Pada module CommonJS, urutannya mengikuti aturan klasik: nextTick queue dulu, baru microtask queue. Pada module ESM, urutan dua lapis pertama justru terbalik: Promise.then mendahului process.nextTick. Penyebabnya ada pada cara Node.js memuat module: evaluasi ESM berjalan melalui mekanisme promise di V8, dan begitu evaluasi selesai, V8 otomatis mengosongkan microtask queue pada titik pemeriksaan (checkpoint) miliknya — sebelum Node.js sempat mengosongkan nextTick queue. Perhatikan juga bahwa pada kedua program di atas, keduanya masih mendahului setTimeout(0): urutan fase timers selalu paling akhir.

Bahaya nextTick Berantai #

Kekuatan “didahulukan sebelum segalanya” membawa konsekuensi serius: nextTick queue harus benar-benar kosong sebelum event loop boleh melanjutkan. Callback nextTick yang mendaftarkan callback nextTick lagi akan membuat antrean tidak pernah habis, dan seluruh event loop terhenti tanpa pernah mencapai fase mana pun:

js Contoh: event loop tidak pernah maju
function ulang() {
  process.nextTick(ulang);
}

process.nextTick(ulang);
setTimeout(() => console.log("tidak pernah tercetak"), 0);

Program di atas akan berjalan tanpa henti tanpa pernah mencetak apa pun, karena callback setTimeout menunggu fase timers yang tidak pernah dikunjungi — event loop terhenti memproses nextTick queue yang terus terisi ulang. Pola ini menelan seluruh kapasitas CPU untuk satu pekerjaan rekursif.

Ringkasan Empat Alat Penundaan #

AlatAntreanDijalankanKapan dipakai
process.nextTick(fn)nextTick queue (Node.js)Tepat setelah callback saat ini, sebelum microtaskKasus khusus: misalnya memastikan error dilempar setelah stack terlepas
Promise.then(fn) / queueMicrotask(fn)microtask queue (V8)Setelah nextTick queue kosongHasil asynchronous, penyisipan ringan antar tugas
setImmediate(fn)fase checkSetelah fase poll pada putaran berjalanMemberi jeda bagi I/O; urutan stabil di dalam callback I/O
setTimeout(fn, 0)fase timers (jeda dibulatkan ke 1 ms)Putaran berikutnya setelah batas waktuPenjadwalan berbasis waktu

Rincian resmi ketiganya tersedia pada panduan event loop, timers, dan process.nextTick , process.nextTick(callback) , dan setImmediate(callback) . Dari sini, jeda berbasis waktu yang lebih panjang dan berulang menjadi topik berikutnya: Chapter Timer dan Scheduling membahas setTimeout, setInterval, pembersihannya, serta cara membuat timer tidak menahan proses tetap hidup.