Callback error-first bekerja, tetapi setiap function asinkron menuntut konvensi yang disepakati sendiri-sendiri, dan sarang callback tumbuh begitu langkah bertambah. JavaScript punya jawaban bawaan sejak ES2015: Promise, sebuah objek standar yang mewakili hasil operasi asinkron — lengkap dengan kontrak seragam untuk sukses, gagal, dan pembersihannya.

Promises adalah fondasi seluruh alat modern di section ini: promise chaining, async-await, hingga combinator. Chapter ini membangun pemahamannya dari nol — tiga state yang dimiliki promise, cara membuatnya sendiri dengan new Promise, dan cara menerima hasilnya lewat then, catch, serta finally.

Tiga State pada Satu Promise #

Setiap promise mengikuti mesin status dengan tiga state:

StateArtiDipicu oleh
pendingpekerjaan masih berjalan, hasil belum adakondisi awal setiap promise
fulfilledpekerjaan selesai disertai nilaipemanggilan resolve(nilai)
rejectedpekerjaan gagal disertai alasanpemanggilan reject(alasan)

Dua aturan mengikat mesin status ini. Pertama, perpindahan state terjadi sekali saja: dari pending menuju fulfilled atau rejected, tanpa jalan kembali; panggilan resolve maupun reject berikutnya diabaikan. Kedua, state tidak bisa dibaca langsung dari luar — kita tidak “menanyakan” status sebuah promise, melainkan mendaftarkan handler yang runtime panggil begitu state berubah. Tiga handler standarnya: then menerima keberhasilan, catch menangkap kegagalan, dan finally berjalan di kedua jalur.

Membuat Promise dengan new Promise #

Berkas berikut membuat dua promise yang berlawanan: satu menuju fulfilled, satu menuju rejected. Keduanya dibangun dengan pola yang sama, dan keduanya diawasi oleh tiga handler:

// promise-dasar.js
// new Promise: executor, resolve, reject, then, catch, finally.

// --- 1. promise yang akan fulfilled ---
const janjiMinuman = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Kopi susu Budi siap diantar");
  }, 20);
});

janjiMinuman
  .then((pesan) => console.log("then:", pesan))
  .catch((error) => console.log("catch:", error.message))
  .finally(() => console.log("finally: status dicatat ke buku kasir"));

// --- 2. promise yang akan rejected ---
const janjiStok = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error("Stok gula habis"));
  }, 20);
});

janjiStok
  .then((pesan) => console.log("then:", pesan))
  .catch((error) => console.log("catch:", error.message))
  .finally(() => console.log("finally: status dicatat ke buku kasir"));

console.log("dua promise dibuat, program tetap berjalan");

Output yang Dihasilkan:

text
dua promise dibuat, program tetap berjalan
then: Kopi susu Budi siap diantar
finally: status dicatat ke buku kasir
catch: Stok gula habis
finally: status dicatat ke buku kasir

Mari kita telaah baris-baris pentingnya:

  1. Baris 5–9 (new Promise pertama): new Promise menerima executor — function yang runtime panggil seketika dengan dua parameter, resolve dan reject. Executor bertanggung jawab menentukan nasib promise.
  2. Baris 7 (resolve(...)): memindahkan janjiMinuman ke state fulfilled dengan nilai string. Panggilan ini terjadi di dalam callback timer, 20 milidetik kemudian.
  3. Baris 11–14 (tiga handler pertama): then menerima nilai fulfilled, catch menampung jalur gagal (tidak terpakai di sini), dan finally mencatat penyelesaian tanpa peduli jalurnya.
  4. Baris 19 (reject(new Error(...))): memindahkan janjiStok ke state rejected. Alasan berupa objek Error agar jejaknya informatif, bukan sekadar teks.
  5. Baris 23–26 (handler kedua): untuk promise rejected, then dilewati dan catch yang menerima alasan; finally tetap berjalan — terlihat dari keluaran yang mencetaknya dua kali.
  6. Baris 28 (log sinkron): tercetak lebih dulu daripada semua handler. Handler promise menunggu di antrean microtask, dan antrean itu baru diproses setelah call stack kosong (chapter Event Loop).

Executor Berjalan Seketika #

Satu sifat executor yang sering mengejutkan: ia dieksekusi segera saat new Promise dipanggil, bukan menunggu pekerjaan selesai. Kita buktikan:

js
// Executor adalah kode sinkron: berjalan sebelum baris berikutnya.
new Promise(() => console.log("executor berjalan seketika"));
console.log("baris sesudahnya");

Output yang Dihasilkan:

text
executor berjalan seketika
baris sesudahnya

Log dari dalam executor mendahului log baris sesudahnya — bukti bahwa executor adalah kode sinkron biasa. Yang asinkron hanyalah kapan resolve atau reject dipanggil, misalnya dari dalam callback timer seperti pada berkas contoh.

Satu Keputusan yang Berlaku #

resolve dan reject boleh sama-sama dipanggil dalam satu executor, tetapi hanya panggilan pertama yang berlaku:

js
// Panggilan resolve/reject kedua diabaikan tanpa galat.
new Promise((resolve, reject) => {
  resolve("diterima lebih dulu");
  reject(new Error("terlambat, tidak berlaku"));
})
  .then((pesan) => console.log("then:", pesan))
  .catch((error) => console.log("catch:", error.message));

Output yang Dihasilkan:

text
then: diterima lebih dulu

resolve dipanggil lebih dulu, sehingga promise berstatus fulfilled; reject berikutnya diabaikan tanpa memunculkan galat apa pun. Begitu state meninggalkan pending, promise terkunci pada hasilnya — persis aturan mesin status di awal chapter.

Dengan kontrak yang seragam ini, beberapa promise bisa disambung menjadi alur yang rapi tanpa sarang callback. Mekanismenya — perantaraan nilai dan penanganan galat antar mata rantai — kita bedah di chapter Promise Chaining.

Berkas Sumber

Berkas pendukung halaman ini dapat diunduh dan dijalankan langsung sesuai penjelasan di atas.

promise-dasar.js

Unduh berkas
javascript
// promise-dasar.js
// new Promise: executor, resolve, reject, then, catch, finally.

// --- 1. promise yang akan fulfilled ---
const janjiMinuman = new Promise((resolve, reject) => {
  setTimeout(() => {
    resolve("Kopi susu Budi siap diantar");
  }, 20);
});

janjiMinuman
  .then((pesan) => console.log("then:", pesan))
  .catch((error) => console.log("catch:", error.message))
  .finally(() => console.log("finally: status dicatat ke buku kasir"));

// --- 2. promise yang akan rejected ---
const janjiStok = new Promise((resolve, reject) => {
  setTimeout(() => {
    reject(new Error("Stok gula habis"));
  }, 20);
});

janjiStok
  .then((pesan) => console.log("then:", pesan))
  .catch((error) => console.log("catch:", error.message))
  .finally(() => console.log("finally: status dicatat ke buku kasir"));

console.log("dua promise dibuat, program tetap berjalan");