Kasir kafe Budi sering menangani beberapa pesanan sekaligus: dua seduhan harus keduanya selesai sebelum nampan diantar, satu layar harus menampilkan laporan lengkap termasuk pesanan yang gagal, dan pemesan berganti meja begitu satu menu tersedia lebih cepat. Satu promise mewakili satu hasil masa depan; menggabungkan beberapa promise menuntut pola yang lebih tinggi.

Untuk kebutuhan itu JavaScript menyediakan empat combinator statis pada Promise — all, allSettled, race, dan any. Semuanya menerima iterable berisi promise dan mengembalikan satu promise baru. Chapter ini membuktikan perilaku keempatnya dengan satu berkas uji, lalu merangkum kapan masing-masing tepat dipakai.

Empat Combinator dalam Satu Berkas #

Berkas berikut memakai satu helper seduh untuk membuat promise pesanan, lalu menjalankan lima percobaan: all sukses, all gagal, allSettled, race, dan any:

// kombinator-promise.js
// Empat combinator: all, allSettled, race, any.

function seduh(nama, ms, gagal = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (gagal) {
        reject(new Error(`${nama} gagal`));
      } else {
        resolve(`${nama} selesai`);
      }
    }, ms);
  });
}

// --- 1. all: berhasil bila SEMUA berhasil ---
Promise.all([seduh("espresso", 30), seduh("latte", 10)])
  .then((hasil) => console.log("1. all:", hasil));

// --- 2. all: gagal cepat bila satu saja gagal ---
Promise.all([seduh("espresso", 30), seduh("latte", 10, true)])
  .catch((error) => console.log("2. all gagal cepat:", error.message));

// --- 3. allSettled: laporan lengkap, sukses maupun gagal ---
Promise.allSettled([seduh("matcha", 20), seduh("cokelat", 10, true)])
  .then((hasil) => {
    for (const entri of hasil) {
      console.log("3. allSettled:", entri.status, entri.value ?? entri.reason.message);
    }
  });

// --- 4. race: hasil pertama yang settle, sukses maupun gagal ---
Promise.race([seduh("teh", 10), seduh("kopi", 30)])
  .then((hasil) => console.log("4. race:", hasil));

// --- 5. any: hasil pertama yang BERHASIL ---
Promise.any([seduh("susu kedelai", 30), seduh("susu oat", 10, true)])
  .then((hasil) => console.log("5. any:", hasil));

Output yang Dihasilkan:

text
2. all gagal cepat: latte gagal
4. race: teh selesai
3. allSettled: fulfilled matcha selesai
3. allSettled: rejected cokelat gagal
1. all: [ 'espresso selesai', 'latte selesai' ]
5. any: susu kedelai selesai

Sebelum membedah, perhatikan hal yang mencolok: nomor pada keluaran tidak berurutan. Itu bukan keanehan — keluaran mengikuti waktu selesainya setiap combinator, dan jeda yang kita pasang sengaja dibuat berbeda. Kita bedah satu per satu.

Promise.all: Semua Harus Berhasil #

Promise.all menunggu semua promise fulfilled, lalu fulfilled dengan array berisi seluruh nilai. Urutan nilainya persis urutan input, bukan urutan selesai — baris keluaran 1 menempatkan espresso (30 ms) di posisi pertama meski latte selesai lebih dulu. Bila satu saja rejected, all langsung rejected dengan alasan promise yang gagal lebih dulu. Itulah perilaku fail-fast pada keluaran 2: latte gagal di 10 ms, dan kegagalan itu segera menjadi hasil akhir meski espresso masih menyeduh.

Promise.allSettled: Laporan Lengkap #

Promise.allSettled menunggu semua promise selesai apa pun statusnya, lalu fulfilled dengan array berisi deskripsi tiap hasil: entri fulfilled berbentuk { status, value }, entri rejected berbentuk { status, reason }. Keluaran 3 menampung keduanya berkat ekspresi entri.value ?? entri.reason.message — nilai hasil berada di value, sedangkan pesan kegagalan berada di reason.message. Karena allSettled tidak pernah rejected, penanganannya nyaman: cukup filter berdasarkan status.

Promise.race: Siapa Pun yang Lebih Dulu #

Promise.race mengembalikan hasil promise pertama yang settle — menang atau kalah. Keluaran 4 menunjukkan teh (10 ms) lebih dulu daripada kopi (30 ms). race sama sekali tidak peduli status: bila promise yang lebih dulu settle justru yang gagal, race langsung rejected dengan alasan itu.

Promise.any: Pertama yang Berhasil #

Promise.any menunggu promise pertama yang fulfilled dan mengabaikan rejection selama masih ada yang bisa berhasil. Keluaran 5 memperlihatkannya: susu oat gagal di 10 ms, tetapi any terus menunggu susu kedelai dan fulfilled bersamanya. Bila semua anggota rejected, any rejected dengan AggregateError — berikut bukti nyatanya:

js
// Semua anggota gagal: any rejected dengan AggregateError.
Promise.any([
  Promise.reject(new Error("server 1 gagal")),
  Promise.reject(new Error("server 2 gagal")),
]).catch((error) => {
  console.log(error.name);
  console.log(error.errors.length);
});

Output yang Dihasilkan:

text
AggregateError
2

Properti errors milik AggregateError menampung seluruh alasan kegagalan dalam urutan input — berguna saat kita perlu melaporkan mengapa semua sumber gagal.

Memilih Combinator yang Tepat #

KombinatorBerhasil bilaGagal bilaCocok untuk
Promise.allsemua fulfilledsatu saja rejected (fail-fast)tugas yang semuanya wajib sukses
Promise.allSettledselalu fulfilledtidak pernahlaporan lengkap sukses dan gagal
Promise.racepromise pertama settlepromise pertama yang settle adalah rejectiontimeout, membandingkan kecepatan
Promise.anypromise pertama yang fulfilledsemua rejected (AggregateError)fallback ke sumber tercepat yang sukses

Perilaku fail-fast Promise.all nyaman saat satu kegagalan memang harus membatalkan semuanya — tetapi menyulitkan bila kita tetap ingin memproses yang berhasil. Cara menangani situasi itu dibedah tuntas di chapter Error Handling Asynchronous Code.

Berkas Sumber

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

kombinator-promise.js

Unduh berkas
javascript
// kombinator-promise.js
// Empat combinator: all, allSettled, race, any.

function seduh(nama, ms, gagal = false) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (gagal) {
        reject(new Error(`${nama} gagal`));
      } else {
        resolve(`${nama} selesai`);
      }
    }, ms);
  });
}

// --- 1. all: berhasil bila SEMUA berhasil ---
Promise.all([seduh("espresso", 30), seduh("latte", 10)])
  .then((hasil) => console.log("1. all:", hasil));

// --- 2. all: gagal cepat bila satu saja gagal ---
Promise.all([seduh("espresso", 30), seduh("latte", 10, true)])
  .catch((error) => console.log("2. all gagal cepat:", error.message));

// --- 3. allSettled: laporan lengkap, sukses maupun gagal ---
Promise.allSettled([seduh("matcha", 20), seduh("cokelat", 10, true)])
  .then((hasil) => {
    for (const entri of hasil) {
      console.log("3. allSettled:", entri.status, entri.value ?? entri.reason.message);
    }
  });

// --- 4. race: hasil pertama yang settle, sukses maupun gagal ---
Promise.race([seduh("teh", 10), seduh("kopi", 30)])
  .then((hasil) => console.log("4. race:", hasil));

// --- 5. any: hasil pertama yang BERHASIL ---
Promise.any([seduh("susu kedelai", 30), seduh("susu oat", 10, true)])
  .then((hasil) => console.log("5. any:", hasil));