Event Loop Deep Dive — Node.js

Semua pelajaran Node.js sebelumnya bilang "Node.js non-blocking" tanpa menjelaskan bagaimana. Jawabannya: event loop. Pondasi: call stack + callback queue JavaS

Semua pelajaran Node.js sebelumnya bilang "Node.js non-blocking" tanpa menjelaskan bagaimana. Jawabannya: event loop.

Pondasi: call stack + callback queue

JavaScript punya satu call stack — hanya bisa eksekusi satu thing pada satu waktu. Jadi bagaimana bisa handle ribuan koneksi?

Kode sync langsung jalan di call stack.
Operasi async (I/O, timer) DIDELEGASIKAN ke sistem (libuv).
Saat selesai, callback-nya dimasukkan ke antrian.
Event loop memindahkan callback dari antrian → call stack saat stack kosong.

libuv: mesin di balik event loop

Node.js memakai libuv (library C) untuk menangani async I/O. libuv menjalankan event loop yang memiliki beberapa fase (phase), dan callback dikelompokkan per fase.

<title id="el-title">Fase-fase Event Loop Node.js</title> Diagram lingkaran fase event loop: timers, pending callbacks, idle prepare, poll, check, close. Di antara setiap fase, microtask queue diproses. 1. timers setTimeout/Interval
  <rect x="400" y="80" width="130" height="46" rx="8" fill="#dbeafe" stroke="#3b82f6"/>
  <text x="465" y="100" text-anchor="middle">2. pending cb</text>
  <text x="465" y="116" text-anchor="middle" font-size="10" fill="#475569">err TCP, dll</text>

  <rect x="400" y="200" width="130" height="46" rx="8" fill="#dcfce7" stroke="#16a34a"/>
  <text x="465" y="220" text-anchor="middle">3. poll</text>
  <text x="465" y="236" text-anchor="middle" font-size="10" fill="#475569">I/O callbacks</text>

  <rect x="230" y="260" width="100" height="46" rx="8" fill="#fae8ff" stroke="#a855f7"/>
  <text x="280" y="280" text-anchor="middle">4. check</text>
  <text x="280" y="296" text-anchor="middle" font-size="10" fill="#475569">setImmediate</text>

  <rect x="30" y="200" width="130" height="46" rx="8" fill="#fee2e2" stroke="#ef4444"/>
  <text x="95" y="220" text-anchor="middle">5. close</text>
  <text x="95" y="236" text-anchor="middle" font-size="10" fill="#475569">socket.on(close)</text>

  <rect x="30" y="80" width="130" height="46" rx="8" fill="#f1f5f9" stroke="#64748b"/>
  <text x="95" y="100" text-anchor="middle">idle/prepare</text>
  <text x="95" y="116" text-anchor="middle" font-size="10" fill="#475569">internal</text>

  <circle cx="280" cy="160" r="48" fill="#eef2ff" stroke="#6366f1" stroke-width="2"/>
  <text x="280" y="156" text-anchor="middle" font-weight="bold">microtask</text>
  <text x="280" y="172" text-anchor="middle" font-size="10">Promise.then</text>
  <text x="280" y="186" text-anchor="middle" font-size="10">queueMicrotask</text>
</g>
<defs>
  <marker id="el-arrow" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="7" markerHeight="7" orient="auto-start-reverse">
    <path d="M0,0 L10,5 L0,10 z" fill="#6366f1"/>
  </marker>
</defs>
<path d="M330,45 A120,120 0 0 1 450,82" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
<path d="M465,128 A120,120 0 0 1 465,198" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
<path d="M450,246 A120,120 0 0 1 335,286" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
<path d="M225,286 A120,120 0 0 1 110,246" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
<path d="M95,198 A120,120 0 0 1 95,128" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
<path d="M110,82 A120,120 0 0 1 225,44" fill="none" stroke="#6366f1" stroke-width="1.5" marker-end="url(#el-arrow)"/>
Urutan fase event loop. Di antara setiap fase (dan setelah setiap callback), microtask queue (Promise.then, queueMicrotask) dihabiskan dulu sebelum lanjut.

Macrotask vs microtask

Macrotask queue — berisi callback dari timer, I/O, setImmediate, dsb. Diproses per fase.

Microtask queue — berisi Promise.then, queueMicrotask, process.nextTick. Di Node.js, microtask queue dihabiskan DULU sebelum lanjut fase berikutnya.

console.log("A");
setTimeout(() => console.log("timer"), 0);
Promise.resolve().then(() => console.log("promise"));
console.log("B");

// Output:
// A
// B
// promise    ← microtask dihabiskan setelah sync code
// timer      ← baru macrotask

process.nextTick vs queueMicrotask vs setImmediate

console.log("sync");
setImmediate(() => console.log("setImmediate"));
setTimeout(() => console.log("setTimeout 0"), 0);
Promise.resolve().then(() => console.log("promise"));
process.nextTick(() => console.log("nextTick"));
console.log("end sync");

// Output:
// sync
// end sync
// nextTick          ← nextTick queue — prioritas TERTINGGI
// promise           ← microtask queue
// setTimeout 0      ← timers phase
// setImmediate      ← check phase

Contoh tricky

setTimeout(() => console.log("timeout"), 0);
setImmediate(() => console.log("immediate"));
// Urutan TIDAK dijamin — tergantung timing startup
import fs from "fs";
fs.readFile("app.js", () => {
  setTimeout(() => console.log("timeout"), 0);
  setImmediate(() => console.log("immediate"));
});
// DI dalam I/O callback → immediate SELALU dulu (karena sudah di poll, next phase = check)

Kenapa ini penting

Yang akan kamu pelajari