Kombinator Promise

Promise.all, allSettled, race, dan any untuk menggabungkan banyak promise.

Daftar isi

Satu promise mewakili satu pekerjaan, sementara program nyata hampir selalu menunggu beberapa pekerjaan sekaligus, misalnya memuat statistik dan profil pada halaman yang sama, atau membandingkan kecepatan beberapa sumber data. Kombinator promise adalah metode statis pada Promise yang menggabungkan sekelompok promise menjadi satu, dengan aturan keberhasilan dan kegagalan yang berbeda-beda. Topik ini mempraktikkan keempatnya, yaitu Promise.all, Promise.allSettled, Promise.race, dan Promise.any, memakai penundaan berbeda agar urutan selesainya dapat diprediksi.

Menjalankan banyak promise dengan Promise.all#

Promise.all menerima sekumpulan promise dan mengembalikan satu promise gabungan. Gabungan itu terpenuhi ketika semua anggotanya terpenuhi, dan nilainya berupa array hasil yang berurut sesuai daftar awal, bukan sesuai urutan selesainya. Sifat ini penting, posisi hasil selalu cocok dengan posisi promise asalnya, sehingga hasil tidak perlu dicari-cari.

js promise-all.js
// Promise.all menunggu semua selesai, hasilnya berurut sesuai daftar awal.
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

const janji = [
  tunggu(40, "statistik"),
  tunggu(20, "profil"),
  tunggu(60, "catatan"),
];

Promise.all(janji).then((hasil) => {
  console.log(hasil);
});
text Keluaran
[ 'statistik', 'profil', 'catatan' ]

Promise kedua, yaitu “profil”, selesai lebih dahulu setelah 20 milidetik, tetapi hasilnya tetap berada di posisi kedua karena urutan keluaran mengikuti daftar awal. Gabungan itu baru terpenuhi setelah anggota terlambatnya, yaitu “catatan” pada milidetik ke-60, selesai. Selama menunggu, tidak ada anggota yang perlu dikhawatirkan kalah cepat karena semuanya sampai pada tempatnya masing-masing.

Semuanya bermula dari pembuatan daftarnya, karena setiap fungsi pembuat promise sudah dipanggil sebelum Promise.all menerima hasilnya. Untuk pekerjaan yang menunggu, seperti permintaan HTTP, jadwal serentak seperti ini sungguh menghemat waktu. Total tunggunya mendekati durasi pekerjaan yang paling lambat, bukan jumlah semuanya. Adapun pekerjaan berat di sisi JavaScript tetap mengantre di call stack, karena pelaksananya tetap satu thread yang sama.

Perilaku tersebut terlihat pada program berikut, yang menaruh satu kegagalan kecil di antara pekerjaan yang baik-baik saja.

js promise-all-gagal.js
// Satu promise yang menolak membuat Promise.all langsung menolak.
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

function gagal(ms, pesan) {
  return new Promise((resolve, reject) => setTimeout(() => reject(new Error(pesan)), ms));
}

Promise.all([tunggu(60, "data utama"), gagal(20, "sensor ketiga mati")])
  .then((hasil) => console.log("berhasil:", hasil))
  .catch((err) => console.log("gagal:", err.message));
text Keluaran
gagal: sensor ketiga mati

“Data utama” sebenarnya siap setelah 60 milidetik, tetapi kegagalan pada milidetik ke-20 sudah mengakhiri semuanya lebih dahulu. Jalur then tidak pernah dicapai, dan satu-satunya informasi yang terselamatkan adalah pesan error anggota yang gagal.

Promise.allSettled ketika semua hasil penting#

Promise.allSettled menunggu seluruh anggota selesai apa pun statusnya, baik terpenuhi maupun menolak. Hasilnya berupa array berisi status akhir setiap anggota, dengan properti status, value untuk yang terpenuhi, dan reason untuk yang menolak. Gabungan ini praktis tidak pernah menolak, sehingga cocok ketika setiap hasil perlu dilaporkan, misalnya memeriksa banyak sensor sekaligus lalu menyimpulkan mana yang hidup dan mana yang mati.

js promise-all-settled.js
// Promise.allSettled mengembalikan status akhir setiap promise.
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

function gagal(ms, pesan) {
  return new Promise((resolve, reject) => setTimeout(() => reject(new Error(pesan)), ms));
}

Promise.allSettled([
  tunggu(20, "sensor pertama"),
  gagal(40, "sensor kedua mati"),
  tunggu(60, "sensor ketiga"),
]).then((hasil) => {
  for (const h of hasil) {
    if (h.status === "fulfilled") {
      console.log(h.status, ":", h.value);
    } else {
      console.log(h.status, ":", h.reason.message);
    }
  }
});
text Keluaran
fulfilled : sensor pertama
rejected : sensor kedua mati
fulfilled : sensor ketiga

Kegagalan sensor kedua tidak merusak yang lain, ketiganya dilaporkan lengkap dengan status akhirnya. Pola perulangan di atas adalah cara lazim membaca hasilnya, nilai diambil dari value bila statusnya fulfilled, dan pesan error diambil dari reason.message bila rejected. Panjang dan urutan array hasilnya sama seperti daftar awalnya, sehingga posisi laporan tetap cocok dengan posisi permintaannya. Pemeriksaannya bisa ditulis dengan metode array biasa, misalnya filter untuk mengumpulkan yang gagal sebelum melapor.

Promise.race dan Promise.any#

Promise.race mengikuti anggota yang selesai paling dulu, apa pun statusnya. Bila anggota tercepat terpenuhi, gabungan terpenuhi dengan nilainya, sedangkan bila anggota tercepat menolak, gabungan ikut menolak. Kegunaan yang lazim adalah pola batas waktu, yakni pekerjaan utama dilombakan dengan timer penolak sehingga pekerjaan yang menggantung lama otomatis kalah balapan. Penulisannya hanya menambahkan satu anggota ke dalam lomba, yaitu promise yang menolak setelah jeda tertentu. Bila pekerjaan utamanya selesai lebih dahulu, penolakan dari timer itu datang terlambat dan tidak berpengaruh.

js promise-race.js
// Promise.race mengikuti promise pertama yang selesai, berhasil atau gagal.
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

Promise.race([tunggu(60, "server cadangan"), tunggu(20, "server utama")])
  .then((hasil) => console.log("pemenang:", hasil));
text Keluaran
pemenang: server utama

Promise.any mirip Promise.race, tetapi hanya keberhasilan yang dihitung. Kegagalan anggota cepat diabaikan, dan gabungan menunggu keberhasilan pertama. Bila seluruh anggota menolak, gabungan menolak dengan AggregateError yang memuat daftar semua alasannya.

js promise-any.js
// Promise.any mengabaikan kegagalan dan mengambil keberhasilan pertama.
function tunggu(ms, nilai) {
  return new Promise((resolve) => setTimeout(() => resolve(nilai), ms));
}

function gagal(ms, pesan) {
  return new Promise((resolve, reject) => setTimeout(() => reject(new Error(pesan)), ms));
}

Promise.any([gagal(20, "cermin pertama mati"), tunggu(40, "cermin kedua aktif")])
  .then((hasil) => console.log("keberhasilan pertama:", hasil));
text Keluaran
keberhasilan pertama: cermin kedua aktif

Memilih kombinator yang tepat#

Keempat kombinator di atas bisa diringkas dengan satu pertanyaan, hasil seperti apa yang dibutuhkan dari sekelompok pekerjaan.

KombinatorTerpenuhi ketikaDitolak ketika
Promise.allsemua anggota terpenuhisatu saja anggota menolak
Promise.allSettledsemua anggota selesai, apa pun statusnyapraktis tidak pernah
Promise.racesatu anggota selesai, apa pun statusnyaanggota tercepat adalah yang menolak
Promise.anysatu anggota terpenuhiseluruh anggota menolak

Aturan pilihannya mengikuti sifat pekerjaannya. Pekerjaan yang saling melengkapi dan semuanya wajib ada ditangani Promise.all, misalnya memuat statistik dan profil untuk satu halaman. Pekerjaan yang hasilnya perlu diperiksa satu per satu memakai Promise.allSettled. Pekerjaan yang hanya butuh sumber tercepat memakai Promise.race atau Promise.any, dengan pembeda bahwa any menoleransi kegagalan di sepanjang jalurnya.

Satu hal berlaku pada semua kombinator, yaitu tidak ada anggota yang dihentikan. Pada Promise.all, anggota yang tersisa tetap berjalan sampai selesai setelah satu anggota menolak, hanya saja hasilnya tidak terpakai. Anggota yang kalah lomba pada Promise.race pun demikian. Pekerjaan yang mahal sebaiknya tidak ditaruh dalam lomba yang kalahnya sia-sia. Alternatifnya adalah memakai mekanisme pembatalan yang disediakan pekerjaannya sendiri.

Ringkasan#

  • Promise.all menunggu semua anggota, hasilnya berurut sesuai daftar awal, dan menolak sekaligus bila satu anggota menolak.
  • Promise.allSettled mengembalikan status akhir setiap anggota, cocok ketika semua hasil perlu dilaporkan.
  • Promise.race mengikuti anggota yang selesai pertama apa pun statusnya, sedangkan Promise.any mengikuti keberhasilan pertama.
  • Pilihan kombinator mengikuti pertanyaannya, semuanya wajib berhasil, semua hasil penting, atau cukup yang tercepat.

Referensi#