Chain promise bagus dalam penanganan error. ketika sebuah promise me-reject, kontrolnya melompat ke handler rejection terdekat. Itu sangat nyaman dalam praktiknya.
Sebagai contoh, kode di bawah sebuah URL ke fetch
itu salah (tidak ada situs seperti itu) dan .catch
menangani error tersebut:
fetch('https://no-such-server.blabla') // reject
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (gagal mengambil resourse, error yang dihasilkan mungkin berbeda)
Seperti yang anda lihat, .catch
tidak harus segera. .catch
mungkin muncul setelah satu atau mungkin beberapa .then
.
Atau, mungkin, semuanya baik-baik saja dengan situs tersebut, tetapi response-nya bukan JSON yang valid. Cara termudah untuk catch semua error adalah menambahkan .catch
pada akhiran chain:
fetch('/article/promise-chaining/user.json')
.then(response => response.json())
.then(user => fetch(`https://api.github.com/users/${user.name}`))
.then(response => response.json())
.then(githubUser => new Promise((resolve, reject) => {
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
setTimeout(() => {
img.remove();
resolve(githubUser);
}, 3000);
}))
.catch(error => alert(error.message));
Biasanya, .catch
semacam itu tidak memicu sama sekali. Tetapi jika salah satu promise di atas me-reject (sebuah masalah jaringan atau json yang tidak valid atau apapun itu), maka promise tersebut akan meng-catch-nya.
try…catch implisit
Kode dari sebuah eksekutor promise dan handler promise memiliki “try..catch
yang tak terlihat” di sekitarnya. Jika terjadi pengecualian, maka pengecualian itu tertangkap dan diperlakukan sebagai rejection.
Sebagai contoh, kode ini:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
…Bekerja persis sama seperti ini:
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
“try..catch
yang tak terlihat” di sekitar eksekutor secara otomatis menangkap error dan mengubahnya menjadi promise yang direject.
Ini terjadi bukan hanya di dalam function eksekutor saja, tetapi di handler-nya juga. Jika kita throw
dalam handler .then
, itu artinya sebuah promise yang direject, jadi kontrolnya melompat ke handler error terdekat.
Ini contohnya:
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // reject promise tersebut
}).catch(alert); // Error: Whoops!
Ini terjadi pada semua error, tidak hanya disebabkan oleh pernyataan throw
. Sebagai contoh, sebuah error pemrograman:
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // tidak ada fungsi seperti ini
}).catch(alert); // ReferenceError: blabla is not defined (blabla tidak terdefinisi)
.catch
terakhir tidak hanya meng-catch rejection secara ekplisit, tetapi juga sesekali error dalam handler di atas.
Melempar kembali
Seperti yang sudah kita perhatikan, .catch
di akhir chain mirip dengan try..catch
. Kita bisa saja memiliki sebanyak mungkin handler .then
seperti yang kita inginkan, dan kemudian menggunakan satu .catch
di akhir untuk menangani error di semuanya.
Di dalam try..catch
biasa kita dapat menganalisis error nya dan mungkin melemparkannya kembali jika tidak bisa ditangani. Hal yang sama mungkin untuk promise.
Jika kita throw
di dalam .catch
, kemudian kontrolnya mengarah ke handler error terdekat selanjutnya. Dan jika kita menangani error dan selesai dengan normal, kemudian berlanjut ke handler .then
sukses terdekat berikutnya.
Pada contoh di bawah ini .catch
sukses menangani error:
// eksekusi: catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
})
.catch(function (error) {
alert("The error is handled, continue normally");
})
.then(() => alert("Next successful handler runs"));
Disini blok .catch
selesai secara normal. Jadi handler .then
yang sukses selanjutnya dipanggil.
Pada contoh di bawah ini kita lihat situasi lain dengan .catch
. Handler (*)
menangkap error dan tidak bisa mengatasinya (misalnya hanya tahu bagaimana menangani URIError
), jadi error-nya dilempar lagi:
// eksekusi: catch -> catch -> then
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// tangani di sini
} else {
alert("Can't handle such error");
throw error; // lemparkan ini atau error lain melompat ke catch selanjutnya
}
}).then(function() {
/* tidak berjalan di sini */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// tidak mengembalikkan apapun => eksekusi berjalan normal
});
Eksekusi tersebut melompat dari .catch
(*)
pertama ke yang selanjutnya (**)
menuruni chain.
Rejection yang tidak tertangani
Apa yang terjadi ketika sebuah error tidak ditangani? Sebagai contoh, kita lupa untuk menambahkan .catch
ke akhir chain, seperti ini:
new Promise(function () {
noSuchFunction(); // Error di sini (tidak ada function seperti itu)
}).then(() => {
// handler promise yang sukses, satu atau lebih
}); // tanpa .catch di akhir!
Jika terjadi error, promise jadi direject, dan eksekusi harus melompat ke handler penolakan terdekat. Tapi tidak ada. Jadi error-nya “stuck”. Tidak ada kode untuk menangani-nya.
Dalam prakteknya, seperti error biasa yang tidak tertangani dalam kode, itu berarti ada sesuatu yang tidak beres.
Apa yang terjadi ketika sebuah error biasa muncul dan tidak tertangkap oleh try..catch
? script-nya mati dengan sebuah pesan di console. Sesuatu yang mirip terjadi dengan rejection promise yang tidak tertangani.
Mesin JavaScript melacak rejection tersebut dan menghasilkan error global dalam kasus itu. Anda dapat melihatnya di console jika anda menjalankan contoh di atas.
Di dalam peramban kita dapat meng-catch kesalahan tersebut menggunakan event unhandledrejection
:
window.addEventListener('unhandledrejection', function(event) {
// object event tersebut memiliki dua properti spesial:
alert(event.promise); // [object Promise] - promise yang menghasilkan error
alert(event.reason); // Error: Whoops! - object error yang tidak tertangani
});
new Promise(function() {
throw new Error("Whoops!");
}); // tidak ada catch untuk menangani error
Event tersebut adalah bagian dari standar HTML.
Jika sebuah error muncul, dan di sana tidak ada .catch
, handler unhandledrejection
terpicu, dan mendapatkan object event
dengan informasi tentang error, jadi kita dapat melakukan sesuatu.
Biasanya error seperti itu tidak dapat dipulihkan, jadi jalan keluar terbaik kita adalah memberi tahu pengguna tentang masalah dan mungkin melaporkan insiden tersebut ke server.
Di dalam lingkungan non-peramban seperti Node.js di sana ada cara lain untuk melacak error yang tak tertangani.
Ringkasan
.catch
menangani error di segala macam promise: baik itu panggilanreject()
, atau sebuah error yang dilemparkan di dalam handler.- Kita harus meletakkan
.catch
tepat di tempat dimana kita ingin menangani error dan tahu bagaimana untuk menangani error-error tersebut. Handler harus menganalisa error (bantuan class error khusus) dan melemparkan kembali yang tidak diketahui (mungkin itu adalah kesalahan pemrograman). - Ok untuk tidak menggunakan
.catch
sama sekali, jika tidak ada cara untuk memulihkan dari kesalahan. - Bagaimanapun kita harus memiliki handler event
unhandledrejection
(untuk peramban,dan analog untuk lingkungan lainnya), untuk melacak error yang tak tertangani dan memberi tahu pengguna (dan mungkin server kita) tentang error-error tersebut, jadi aplikasi kita tidak pernah “mati begitu saja”.