Ada 6 method static di dalam class Promise
. Kita akan segera membahas kasus penggunaan method-method tersebut di sini.
Promise.all
Katakanlah kita ingin menjalankan banyak promise untuk dieksekusi secara paralel, dan menunggu sampai semua promise tersebut siap.
Sebagai contoh, unduh beberapa URL secara paralel dan proses isinya ketika semuanya selesai.
Itulah gunanya Promise.all
.
Sintaksis nya adalah:
let promise = Promise.all([...promises...]);
Promise.all
mengambil sebuah array promise (secara teknis bisa menjadi iterable, tetapi biasanya sebuah array) dan mengembalikkan promise baru.
Promise baru resolve ketika semua promise yang terdaftar diselesaikan dan array dari hasil promise menjadi hasilnya itu sendiri.
Sebagai contoh, Promise.all
di bawah selesai setelah 3 detik, dan kemudian hasilnya adalah sebuah array [1, 2, 3]
:
Promise.all([
new Promise((resolve) => setTimeout(() => resolve(1), 3000)), // 1
new Promise((resolve) => setTimeout(() => resolve(2), 2000)), // 2
new Promise((resolve) => setTimeout(() => resolve(3), 1000)), // 3
]).then(alert); // 1,2,3 ketika promise sudah siap: setiap promise menyumbangkan sebuah member array
Harap dicatat bahwa urutan member array yang dihasilkan adalah sama dengan sumber promise. Meskipun promise pertama membutuhkan waktu yang lama untuk resolve, promise tersebut masih yang pertama di dalam hasil array.
Sebuah trik umum adalah untuk memetakan sebuah array dari data pekerjaan kedalam array promise, dan kemudian membungkusnya kedalam Promise.all
.
Sebagai contoh, jika kita memiliki array URL, kita dapat mengambil array-array tersebut seperti ini:
let urls = [
"https://api.github.com/users/iliakan",
"https://api.github.com/users/remy",
"https://api.github.com/users/jeresig",
];
// memetakan setiap url ke pengambilan promise
let requests = urls.map((url) => fetch(url));
// Promise.all menunggu sampai semua pekerjaan telah diresolve
Promise.all(requests).then((responses) =>
responses.forEach((response) => alert(`${response.url}: ${response.status}`))
);
Contoh terbesar dengan mengambil informasi pengguna untuk sebuah array pengguna GitHub dengan nama mereka (kita dapat mengambil array dengan id mereka, logika nya sama):
let names = ["iliakan", "remy", "jeresig"];
let requests = names.map((name) =>
fetch(`https://api.github.com/users/${name}`)
);
Promise.all(requests)
.then((responses) => {
// semua response telah sukses diresolve
for (let response of responses) {
alert(`${response.url}: ${response.status}`); // menunjukkan 200 untuk setiap url
}
return responses;
})
// memetakan array response kedalam array response.json() untuk membaca isinya
.then((responses) => Promise.all(responses.map((r) => r.json())))
// semua jawaban JSON diuraikan: "users" adalah array dari jawaban tersebut
.then((users) => users.forEach((user) => alert(user.name)));
Jika ada promise yang direject, promise tersebut dikembalikkan oleh Promise.all
secara langsung me-reject nya dengan error itu.
Sebagai contoh:
Promise.all([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).catch(alert); // Error: Whoops!
Disini promise kedua reject dalam dua detik. Itu langsung mengarah pada rejection Promise.all
, jadi eksekusi .catch
: error rejection menjadi hasil keseluruhan Promise.all
.
Jika satu promise reject, Promise.all
langsung reject, benar-benar melupakan yang lainnya yang ada di dalam daftar. Hasil dari promise-promise tersebut diabaikan.
Sebagai contoh, jika disana ada banyak pemanggilan fetch
, seperti contoh di atas, dan satu gagal, yang lainnya akan terus mengeksekusi, tetapi Promise.all
tidak akan memperhatikan promise-promisenya lagi. Promise-promise tersebut mungkin selesai, tetapi hasilnya akan diabaikan.
Promise.all
tidak melakukan apapun untuk membatalkan promise-promise tersebut, karena tidak ada konsep “pembatalan” di dalam promise. Di bab lainnya kita akan membahas AbortController
yang bisa membantu, tetapi AbortController
tersebut bukan bagian dari API Promise.
Promise.all(iterable)
memungkinkan nilai “regular” non-promise di dalam iterable
Sebagai contoh, berikut hasilnya [1, 2, 3]
:
Promise.all([
new Promise((resolve, reject) => {
setTimeout(() => resolve(1), 1000);
}),
2,
3,
]).then(alert); // 1, 2, 3
Jadi kita dapat meneruskan nilai yang sudah siap ke Promise.all
jika nyaman.
Promise.allSettled
Promise.all
reject seluruhnya jika ada promise yang reject. Itu bagus untuk kasus “semua atau tidak sama sekali”, ketika kita membutuhkan semua hasil untuk melanjutkan:
Promise.all([
fetch('/template.html'),
fetch('/style.css'),
fetch('/data.json')
]).then(render); // method render butuh hasil dari semua pengambilan
Promise.allSettled
menunggu semua promise selesai. Array yang dihasilkan memiliki:
{status:"fulfilled", value:result}
untuk response sukses,{status:"rejected", reason:error}
untuk error.
Sebagai contoh, kita ingin mengambil informasi tentang banyak pengguna. Bahkan jika satu request gagal, kita masih tertarik pada yang request yang lain.
Mari gunakan Promise.allSettled
:
let urls = [
"https://api.github.com/users/iliakan",
"https://api.github.com/users/remy",
"https://no-such-url",
];
Promise.allSettled(urls.map((url) => fetch(url))).then((results) => {
// (*)
results.forEach((result, num) => {
if (result.status == "fulfilled") {
alert(`${urls[num]}: ${result.value.status}`);
}
if (result.status == "rejected") {
alert(`${urls[num]}: ${result.reason}`);
}
});
});
results
pada baris (*)
di atas akan:
[
{status: 'fulfilled', value: ...response...},
{status: 'fulfilled', value: ...response...},
{status: 'rejected', reason: ...error object...}
]
Jadi, untuk setiap promise kita mendapatkan status-nya dan value/error
.
Polyfill
Jika peramban tidak mendukung Promise.allSettled
, mudah untuk melakukan polyfill:
if (!Promise.allSettled) {
const rejectHandler = reason => ({ status: 'rejected', reason });
const resolveHandler = value => ({ status: 'fulfilled', value });
Promise.allSettled = function (promises) {
const convertedPromises = promises.map(p => Promise.resolve(p).then(resolveHandler, rejectHandler));
return Promise.all(convertedPromises);
};
}
Dalam kode ini, promises.map
mengambil nilai input, berubah menjadi promises (untuk berjaga-jaga jika non-promise yang diteruskan) dengan p => Promise.resolve(p)
, dan kemudian menambahkan handler .then
handler ke semuanya.
Handler tersebut mengubah hasil v
yang sukses menjadi {state:'fulfilled', value:v}
, dan sebuah error r
menjadi {state:'rejected', reason:r}
. Itu persis dengan format Promise.allSettled
.
Kita bisa menggunakan Promise.allSettled
untuk mendapatkan hasilnya atau semua promise diberikan, bahkan jika beberapa dari promise itu reject.
Promise.race
Mirip dengan Promise.all
, tetapi hanya menunggu promise pertama diselesaikan, dan mendapatkan hasilnya (atau error).
Sintaksisnya adalah:
let promise = Promise.race(iterable);
Sebagai contoh, hasil di sini akan menjadi 1
:
Promise.race([
new Promise((resolve, reject) => setTimeout(() => resolve(1), 1000)),
new Promise((resolve, reject) =>
setTimeout(() => reject(new Error("Whoops!")), 2000)
),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000)),
]).then(alert); // 1
Promise pertama di sini adalah yang tercepat, jadi promise tersebut menjadi hasilnya. Setelah promise pertama yang selesai “memenangkan balapan”, semua hasil/errors lebih lanjut akan diabaikan.
Promise.any
Similar to Promise.race
, but waits only for the first fulfilled promise and gets its result. If all of the given promises are rejected, then the returned promise is rejected with AggregateError
– a special error object that stores all promise errors in its errors
property.
The syntax is:
let promise = Promise.any(iterable);
For instance, here the result will be 1
:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Whoops!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => resolve(1), 2000)),
new Promise((resolve, reject) => setTimeout(() => resolve(3), 3000))
]).then(alert); // 1
The first promise here was fastest, but it was rejected, so the second promise became the result. After the first fulfilled promise “wins the race”, all further results are ignored.
Here’s an example when all promises fail:
Promise.any([
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Ouch!")), 1000)),
new Promise((resolve, reject) => setTimeout(() => reject(new Error("Error!")), 2000))
]).catch(error => {
console.log(error.constructor.name); // AggregateError
console.log(error.errors[0]); // Error: Ouch!
console.log(error.errors[1]); // Error: Error
});
As you can see, error objects for failed promises are available in the errors
property of the AggregateError
object.
Promise.resolve/reject
Method Promise.resolve
dan Promise.reject
jarang dibutuhkan dalam kode modern, karena sintaksis async/await
(kita akan membahasnya nanti) membuat method-method tersebut usang.
Kita membahas method-method tersebut di sini sebagai pelengkap, dan bagi mereka yang tidak bisa menggunakan async/await
untuk beberapa alasan.
Promise.resolve(value)
membuat promise yang diresolve dengan hasilvalue
.
Sama dengan:
let promise = new Promise((resolve) => resolve(value));
Method ini digunakan untuk kompatibilitas, ketika function diharapkan untuk mengembalikkan promise.
Sebagai contoh, function loadCached
di bawah mengambil URL dan mengingat (cache) isinya. Untuk panggilan di masa mendatang dengan URL yang sama itu segera mendapatkan isi sebelumnya dari cache, tetapi menggunakan Promise.resolve
untuk membuat promise tentang itu, jadi hasil yang dikembalikkan selalu sebuah promise:
let cache = new Map();
function loadCached(url) {
if (cache.has(url)) {
return Promise.resolve(cache.get(url)); // (*)
}
return fetch(url)
.then(response => response.text())
.then(text => {
cache.set(url,text);
return text;
});
}
Kita dapat menulis loadCached(url).then(…)
, karena function tersebut dijamin untuk mengembalikan promise. Kita selalu dapat menggunakan .then
setelah loadCached
. Itulah tujuan dari Promise.resolve
pada baris (*)
.
Promise.reject
Promise.reject(error)
membuat promise yang direjecet denganerror
.
Sama dengan:
let promise = new Promise((resolve, reject) => reject(error));
Dalam praktiknya, method ini hampir tidak pernah digunakan.
Ringkasan
Ada 6 method static dari class Promise
:
Promise.all(promises)
– menunggu semua promise selesai dan mengambalikan sebuah array sebagai hasilnya. Jika salah satu promises yang diberikan reject, maka menjadi error ofPromise.all
, dan semua hasil lainnya akan diabaikan.Promise.allSettled(promises)
(method yang baru ditambahkan) – menunggu semua promise selesai dan mengembalikan hasilnya sebagai objek array dengan:state
:"fulfilled"
or"rejected"
value
(jika fulfilled) ataureason
(jika rejected).
Promise.race(promises)
– menunggu promise pertama selesai, dan hasil/error menjadi hasilnya.Promise.any(promises)
(metode baru yang ditambahkan) – menunggu promise pertama terpenuhi, dan hasilnya menjadi hasil keseluruhan. Jika semua janji yang diberikan ditolak, [AggregateError
] (mdn: js / AggregateError) menjadi erorPromise.any
.Promise.resolve(value)
– membuat promise yang resolved dengan nilai yang diberikan.Promise.reject(error)
– membuat promise rejected dengan error yang diberikan.
Dari semua ini, Promise.all
mungkin yang paling umum dalam praktiknya.