Async/Await di Node.js — Node.js

Node.js bersifat asynchronous — operasi I/O tidak memblokir eksekusi. Callback (cara lama): fs.readFile("data.txt", (err, data) => { if (err) throw err…

Kode asynchronous (async) adalah kode yang memulai pekerjaan lambat, misalnya membaca file atau bertanya ke database, lalu lanjut mengerjakan hal lain sambil menunggu hasilnya. Bayangin warung kopi dengan satu barista. Dia menerima pesanan pertama, menaruh biji kopi di mesin giling, dan selagi mesinnya bekerja sendiri, dia menerima pesanan kedua. Begitu mesin berbunyi "tit", dia lanjutkan pesanan pertama. Barista itu JavaScript di Node (cuma satu), mesin gilingnya sistem operasi yang mengerjakan I/O, dan bunyi "tit" itu hasilnya yang sudah siap.

🧪 Tebakan cepat: ambilCatatan() butuh 300 ms dan ambilPengguna() butuh 200 ms. Kalau kamu menulis await ambilCatatan() lalu await ambilPengguna(), berapa lama totalnya? Kalau keduanya dimasukkan ke Promise.all? Tebak dulu, jawabannya ada di bagian "Berurutan atau bersamaan".

Blocking dan non-blocking

I/O (input/output) adalah semua pekerjaan yang menunggu sesuatu di luar program: disk, jaringan, database. Node memakai model non-blocking I/O: selagi satu file dibaca, Node tetap bisa menjawab request lain. Barista yang blocking berdiri menatap mesin giling sampai selesai, dan antrean di belakangnya ikut berhenti. Di Node, versi blocking itu yang berakhiran Sync, seperti fs.readFileSync. Di server, pakai versi yang gak memblokir.

Tiga cara menunggu hasil

Tugasnya sama: baca data/catatan.json milik catatan-api, lalu hitung isinya.

1. Callback, cara paling lama. Kamu menitipkan function yang dipanggil nanti, saat file selesai dibaca:

const fs = require("node:fs");

fs.readFile("data/catatan.json", "utf8", (err, isi) => {
  if (err) {
    console.log("Gagal: " + err.message);
    return;
  }
  console.log(JSON.parse(isi).length + " catatan");
});

Kalau langkah kedua butuh hasil langkah pertama, callback-nya masuk ke callback, lalu ke callback lagi. Kodenya menjorok makin dalam ke kanan: itu yang disebut callback hell.

2. Promise. fs.promises.readFile (sama dengan require("node:fs/promises").readFile) langsung mengembalikan Promise: object janji yang nanti berhasil (resolve) dengan isinya atau gagal (reject) dengan error. .then untuk hasil, .catch untuk error:

fs.promises.readFile("data/catatan.json", "utf8")
  .then((isi) => console.log(JSON.parse(isi).length + " catatan"))
  .catch((err) => console.log("Gagal: " + err.message));

3. async/await. Masih Promise yang sama, tapi dibaca dari atas ke bawah. await hanya boleh di dalam function yang ditandai async, dan error-nya ditangkap dengan try/catch biasa:

async function hitungCatatan() {
  try {
    const isi = await fs.promises.readFile("data/catatan.json", "utf8");
    console.log(JSON.parse(isi).length + " catatan");
  } catch (err) {
    console.log("Gagal: " + err.message);
  }
}
hitungCatatan();

Ketiganya mencetak 2 catatan. Kalau file-nya gak ada, ketiganya mencetak Gagal: ENOENT: no such file or directory, open 'data/catatan.json'.

await menunggu di dalam function, Node tetap jalan

async function main() {
  console.log("1. mulai baca");
  const isi = await fs.promises.readFile("data/catatan.json", "utf8");
  console.log("3. selesai: " + JSON.parse(isi).length + " catatan");
}
main();
console.log("2. baris setelah main()");
1. mulai baca
2. baris setelah main()
3. selesai: 2 catatan

await cuma menghentikan main sampai Promise-nya selesai. Kode di luar main jalan terus, jadi baris 2 tercetak duluan. Hal yang sama berlaku untuk timer: console.log("1"); setTimeout(() => console.log("2"), 0); console.log("3"); mencetak 1, 3, lalu 2. Callback, walau tundanya 0, baru jalan setelah semua kode sinkron selesai.

Berurutan atau bersamaan

Supaya waktunya kelihatan, kita tiru pekerjaan lambat dengan setTimeout yang dibungkus Promise:

function tunda(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}
const ambilCatatan = () => tunda(300, ["Belanja", "Belajar"]);
const ambilPengguna = () => tunda(200, ["Budi"]);

async function main() {
  let mulai = Date.now();
  const catatan = await ambilCatatan();
  const pengguna = await ambilPengguna();
  console.log("berurutan: " + (Date.now() - mulai) + " ms");

  mulai = Date.now();
  const [c2, p2] = await Promise.all([ambilCatatan(), ambilPengguna()]);
  console.log("Promise.all: " + (Date.now() - mulai) + " ms");
}
main();
berurutan: 501 ms
Promise.all: 300 ms

Dua await berturut-turut menunggu satu per satu: 300 + 200 = 500 ms. Promise.all([...]) memulai keduanya sekaligus dan menunggu sampai semuanya selesai, jadi totalnya sama dengan yang paling lama, 300 ms. Itu jawaban tebakannya. Hasilnya array dengan urutan yang sama seperti daftar yang kamu berikan, bukan urutan selesainya.

Pakai Promise.all kalau pekerjaannya gak saling bergantung. Kalau satu saja gagal, Promise.all langsung gagal dengan error itu. Butuh hasil semuanya, termasuk yang gagal? Promise.allSettled menunggu semuanya dan memberi status fulfilled atau rejected untuk masing-masing.

Di latihan: fetchData dan fetchWithError

Kode awal sudah punya fetchData() (Promise yang berhasil dengan dua item) dan fetchWithError() (Promise yang ditolak dengan Koneksi timeout). Di main(), ubah baris berhasilnya jadi console.log("Data: " + JSON.stringify(data));, lalu tambahkan try/catch kedua yang menjalankan await fetchWithError(); dan di catch mencetak "Gagal: " + err.message:

Mengambil data...
Data: [{"id":1,"item":"Buku"},{"id":2,"item":"Pensil"}]
Mengambil data error...
Gagal: Koneksi timeout

⚠️ Jebakan yang sering ketemu

TL;DR: Node memakai non-blocking I/O: pekerjaan lambat dimulai, lalu Node lanjut mengerjakan hal lain. Hasilnya diterima lewat callback (cara lama, gampang jadi callback hell), Promise (.then/.catch), atau async/await dengan try/catch (paling mudah dibaca). await cuma menghentikan function async-nya, bukan seluruh program, dan callback setTimeout(..., 0) tetap jalan setelah kode sinkron (1 3 2). fs.promises.readFile membaca file dengan Promise. Dua await berurutan menjumlahkan waktunya (300 + 200 = 500 ms); Promise.all menjalankannya bersamaan (300 ms) dan langsung gagal kalau satu gagal. Ambil pesan error dari err.message yang tertangkap.

Yang akan kamu pelajari