Alur nyata hampir selalu lebih dari satu langkah: pesanan diterima, dibayar, diseduh, lalu disajikan — dan setiap langkah asinkron membutuhkan hasil langkah sebelumnya. Dengan callback, langkah seperti ini bersarang semakin dalam sebagaimana kita lihat di chapter Callbacks. Dengan promise, langkah yang sama menjadi deretan then yang lurus, karena setiap then mengembalikan promise baru yang siap disambung lagi.

Chapter ini membedah tiga aturan yang membuat rantai bekerja: nilai yang mengalir antar mata rantai, promise yang dikembalikan dari dalam then, serta perilaku catch baik di ujung maupun di tengah rantai.

Setiap then Mengembalikan Promise Baru #

Aturan inti chaining: method then selalu mengembalikan promise baru — bukan promise yang sama, dan bukan nilai biasa. Runtime menyambung mata rantainya dengan kontrak sederhana:

  • Bila handler mengembalikan nilai biasa, promise hasil rantai fulfilled dengan nilai itu.
  • Bila handler mengembalikan promise, promise hasil menunggu promise tersebut: status dan nilainya ditiru sampai tuntas.
  • Bila handler melempar error, promise hasil berstatus rejected dengan alasan tersebut.

Karena hasil then lagi-lagi promise, memanggil then di atasnya adalah sah — dan begitulah rantai terbentuk, satu mata rantai per langkah.

Nilai Mengalir dari Langkah ke Langkah #

Berkas berikut merangkai tiga langkah pesanan kafe. Perhatikan dua bentuk kembalian yang berbeda: langkah pertama mengembalikan promise baru, langkah kedua mengembalikan string biasa:

// rantai-pesanan.js
// then berantai: nilai mengalir dari langkah ke langkah.

function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

// --- 1. rantai tiga langkah pesanan ---
tunggu(20, "Pesanan #12: kopi susu")
  .then((pesanan) => {
    console.log("diterima :", pesanan);
    return tunggu(20, `${pesanan} + extra shot`); // kembalikan promise baru
  })
  .then((pesanan) => {
    console.log("diseduh  :", pesanan);
    return `Struk untuk ${pesanan}`; // kembalikan nilai biasa
  })
  .then((struk) => {
    console.log(struk);
  })
  .catch((error) => console.log("gagal:", error.message));

Output yang Dihasilkan:

text
diterima : Pesanan #12: kopi susu
diseduh  : Pesanan #12: kopi susu + extra shot
Struk untuk Pesanan #12: kopi susu + extra shot

Mari kita telaah baris-baris pentingnya:

  1. Baris 4–6 (tunggu): helper asinkron kecil yang mengembalikan promise; resolve dipanggil dari callback setTimeout, sehingga setiap langkah punya jeda nyata.
  2. Baris 9 (langkah pertama): menghasilkan promise berisi teks pesanan — bahan mentah seluruh rantai.
  3. Baris 10–13 (handler pertama): membaca nilai, mencetaknya, lalu mengembalikan promise baru pada baris 12. Rantai menunggu promise itu selesai sebelum handler berikutnya dipanggil — inilah cara menyambung dua operasi asinkron berurutan tanpa sarang.
  4. Baris 14–17 (handler kedua): menerima nilai dari promise yang ditiru (... + extra shot), lalu mengembalikan string biasa. Promise berikutnya fulfilled dengan string itu seketika.
  5. Baris 18–21 (handler ketiga dan catch): handler ketiga membaca struk dan mencetaknya; catch di ujung menampung kegagalan dari langkah mana pun dalam rantai.

throw Melompat ke catch Terdekat #

Satu throw di tengah rantai membuat semua then berikutnya dilewati; eksekusi melompat ke catch terdekat. Handler kedua pada cuplikan ini tidak pernah berjalan:

js
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

tunggu(10, "pembayaran")
  .then((langkah) => {
    console.log(langkah, "terverifikasi");
    throw new Error("Kartu ditolak bank penerbit");
  })
  .then((langkah) => console.log("langkah ini dilewati:", langkah))
  .catch((error) => console.log("tertangkap di akhir:", error.message));

Output yang Dihasilkan:

text
pembayaran terverifikasi
tertangkap di akhir: Kartu ditolak bank penerbit

Perilaku ini menggantikan penulisan ulang penanganan galat di setiap lapis sarang callback: satu throw cukup, dan seluruh then di antaranya ikut dilewati. Rantai hanya butuh satu catch di ujung untuk menampung kegagalan dari langkah mana pun.

catch di Tengah: Rantai yang Pulih #

catch boleh berada di tengah rantai — dan begitu penanganannya selesai, rantai kembali normal. Pada cuplikan ini catch mengubah kegagalan menjadi nilai pengganti:

js
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

tunggu(10, "kopi arabika")
  .then((stok) => {
    throw new Error(`Stok ${stok} habis`);
  })
  .catch((error) => {
    console.log("pemulihan:", error.message);
    return "kopi robusta"; // nilai pemulihan melanjutkan rantai
  })
  .then((stokPengganti) => console.log("pesanan lanjut:", stokPengganti));

Output yang Dihasilkan:

text
pemulihan: Stok kopi arabika habis
pesanan lanjut: kopi robusta

Nilai yang dikembalikan catch diperlakukan seperti kembalian handler biasa, sehingga then sesudahnya menerima "kopi robusta" — bukan kegagalan. Inilah bedanya catch tengah dan catch akhir: yang di tengah memulihkan alur, yang di ujung menjadi penampung terakhir untuk kegagalan yang tidak dipulihkan. Rantai yang sehat lazimnya memakai keduanya.

Rantai then sudah jauh lebih rapi daripada callback bersarang, tetapi JavaScript punya bentuk yang lebih dekat dengan cara kita membaca kode: async dan await. Kita bangun di chapter berikutnya.

Berkas Sumber

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

rantai-pesanan.js

Unduh berkas
javascript
// rantai-pesanan.js
// then berantai: nilai mengalir dari langkah ke langkah.

function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

// --- 1. rantai tiga langkah pesanan ---
tunggu(20, "Pesanan #12: kopi susu")
  .then((pesanan) => {
    console.log("diterima :", pesanan);
    return tunggu(20, `${pesanan} + extra shot`); // kembalikan promise baru
  })
  .then((pesanan) => {
    console.log("diseduh  :", pesanan);
    return `Struk untuk ${pesanan}`; // kembalikan nilai biasa
  })
  .then((struk) => {
    console.log(struk);
  })
  .catch((error) => console.log("gagal:", error.message));