Rantai janji sangat bagus dalam penanganan kesalahan. Ketika sebuah janji ditolak, kendali berpindah ke pengendali penolakan terdekat. Itu sangat nyaman dalam praktiknya.
Misalnya, pada kode di bawah, URL yang akan fetch
salah (tidak ada situs seperti itu) dan .catch
menangani kesalahan tersebut:
ambil('https://no-such-server.blabla') // ditolak .then(respon => respon.json()) .catch(err => alert(err)) // TypeError: gagal diambil (teks mungkin berbeda)
Seperti yang Anda lihat, .catch
tidak harus bersifat langsung. Ini mungkin muncul setelah satu atau mungkin beberapa .then
.
Atau mungkin situsnya baik-baik saja, tetapi responsnya bukan JSON yang valid. Cara termudah untuk menangkap semua kesalahan adalah dengan menambahkan .catch
ke akhir rantai:
ambil('https://javascript.info/article/promise-chaining/user.json') .then(respon => respon.json()) .then(pengguna => ambil(`https://api.github.com/users/${user.name}`)) .then(respon => respon.json()) .then(githubUser => janji baru((putuskan, tolak) => { biarkan img = dokumen.createElement('img'); img.src = githubUser.avatar_url; img.className = "contoh-avatar-janji"; dokumen.body.append(img); setWaktu habis(() => { img.hapus(); tekad(githubUser); }, 3000); })) .catch(error => alert(error.message));
Biasanya, .catch
seperti itu tidak terpicu sama sekali. Tetapi jika salah satu janji di atas ditolak (masalah jaringan atau json tidak valid atau apa pun), maka janji tersebut akan ditangkap.
Kode pelaksana janji dan penangan janji memiliki “ try..catch
” yang tak terlihat di sekelilingnya. Jika terjadi pengecualian, pengecualian tersebut akan ditangkap dan dianggap sebagai penolakan.
Misalnya, kode ini:
Janji baru((putuskan, tolak) => { melempar Kesalahan baru("Ups!"); }).menangkap(waspada); // Kesalahan: Ups!
…Bekerja persis sama seperti ini:
Janji baru((putuskan, tolak) => { tolak(Kesalahan baru("Ups!")); }).menangkap(waspada); // Kesalahan: Ups!
" try..catch
tak terlihat" di sekitar eksekutor secara otomatis menangkap kesalahan dan mengubahnya menjadi janji yang ditolak.
Hal ini terjadi tidak hanya pada fungsi eksekutor, tetapi juga pada penangannya. Jika kita throw
ke dalam handler .then
, itu berarti janji ditolak, sehingga kontrol melompat ke handler error terdekat.
Berikut ini contohnya:
Janji baru((putuskan, tolak) => { tekad("oke"); }).lalu((hasil) => { melempar Kesalahan baru("Ups!"); // menolak janji itu }).menangkap(waspada); // Kesalahan: Ups!
Hal ini terjadi untuk semua kesalahan, bukan hanya kesalahan yang disebabkan oleh pernyataan throw
. Misalnya, kesalahan pemrograman:
Janji baru((putuskan, tolak) => { tekad("oke"); }).lalu((hasil) => { blabla(); // tidak ada fungsi seperti itu }).menangkap(waspada); //ReferenceError: blabla tidak ditentukan
.catch
terakhir tidak hanya menangkap penolakan eksplisit, tetapi juga kesalahan yang tidak disengaja pada penangan di atas.
Seperti yang telah kita ketahui, .catch
di akhir rantai mirip dengan try..catch
. Kita mungkin memiliki penangan .then
sebanyak yang kita inginkan, dan kemudian menggunakan satu .catch
di akhir untuk menangani kesalahan pada semuanya.
Dalam try..catch
biasa kita dapat menganalisis kesalahan dan mungkin mengulanginya jika tidak dapat ditangani. Hal yang sama juga mungkin terjadi pada janji.
Jika kita throw
.catch
ke dalam, maka kontrol akan berpindah ke penangan kesalahan terdekat berikutnya. Dan jika error tersebut kita tangani dan selesai secara normal, maka error tersebut akan dilanjutkan ke handler .then
terdekat berikutnya yang berhasil.
Pada contoh di bawah, .catch
berhasil menangani kesalahan:
// eksekusi: catch -> lalu Janji baru((putuskan, tolak) => { melempar Kesalahan baru("Ups!"); }).menangkap(fungsi(kesalahan) { alert("Error sudah ditangani, lanjutkan seperti biasa"); }).then(() => alert("Penangan berikutnya berhasil dijalankan"));
Di sini blok .catch
selesai secara normal. Jadi handler .then
sukses berikutnya dipanggil.
Pada contoh di bawah ini kita melihat situasi lain dengan .catch
. Penangan (*)
menangkap kesalahan tersebut dan tidak dapat mengatasinya (misalnya ia hanya mengetahui cara menangani URIError
), sehingga ia melemparkannya lagi:
// eksekusi: catch -> catch Janji baru((putuskan, tolak) => { melempar Kesalahan baru("Ups!"); }).menangkap(fungsi(kesalahan) { // (*) if (kesalahan instance dari URIError) { // menanganinya } kalau tidak { alert("Tidak dapat menangani kesalahan seperti itu"); kesalahan melempar; // melemparkan kesalahan ini atau kesalahan lainnya, melompat ke tangkapan berikutnya } }).lalu(fungsi() { /* tidak berjalan di sini */ }).menangkap(kesalahan => { // (**) alert(`Terjadi kesalahan yang tidak diketahui: ${error}`); // jangan kembalikan apa pun => eksekusi berjalan seperti biasa });
Eksekusinya melompat dari .catch
pertama (*)
ke .catch berikutnya (**)
dalam rantai.
Apa yang terjadi jika kesalahan tidak ditangani? Misalnya, kita lupa menambahkan .catch
ke akhir rantai, seperti di sini:
Janji baru(fungsi() { tidak adaFungsi Seperti itu(); // Kesalahan di sini (tidak ada fungsi seperti itu) }) .lalu(() => { // penangan janji yang berhasil, satu atau lebih }); // tanpa .catch di akhir!
Jika terjadi kesalahan, janji ditolak, dan eksekusi harus melompat ke penangan penolakan terdekat. Tapi tidak ada satu pun. Jadi kesalahannya “macet”. Tidak ada kode untuk menanganinya.
Dalam praktiknya, seperti halnya kesalahan kode yang tidak tertangani, ini berarti ada sesuatu yang tidak beres.
Apa yang terjadi jika kesalahan biasa terjadi dan tidak tertangkap oleh try..catch
? Skrip mati dengan pesan di konsol. Hal serupa terjadi pada penolakan janji yang tidak tertangani.
Mesin JavaScript melacak penolakan tersebut dan menghasilkan kesalahan global dalam kasus tersebut. Anda dapat melihatnya di konsol jika menjalankan contoh di atas.
Di browser kita dapat menangkap kesalahan tersebut menggunakan event unhandledrejection
:
window.addEventListener('penolakan tidak tertangani', fungsi(peristiwa) { // objek event mempunyai dua properti khusus: alert(event.janji); // [object Promise] - janji yang menghasilkan kesalahan alert(event.alasan); // Kesalahan: Ups! - objek kesalahan yang tidak tertangani }); Janji baru(fungsi() { melempar Kesalahan baru("Ups!"); }); // tidak ada tangkapan untuk menangani kesalahan
Acara ini adalah bagian dari standar HTML.
Jika terjadi kesalahan, dan tidak ada .catch
, penangan unhandledrejection
akan terpicu, dan mendapatkan objek event
dengan informasi tentang kesalahan tersebut, sehingga kita dapat melakukan sesuatu.
Biasanya kesalahan seperti itu tidak dapat diperbaiki, jadi jalan keluar terbaik kami adalah memberi tahu pengguna tentang masalah tersebut dan mungkin melaporkan kejadian tersebut ke server.
Di lingkungan non-browser seperti Node.js, ada cara lain untuk melacak kesalahan yang tidak tertangani.
.catch
menangani semua jenis kesalahan dalam janji: baik itu panggilan reject()
, atau kesalahan yang terjadi pada handler.
.then
juga menangkap kesalahan dengan cara yang sama, jika diberikan argumen kedua (yaitu penangan kesalahan).
Kita harus menempatkan .catch
tepat di tempat di mana kita ingin menangani kesalahan dan mengetahui cara menanganinya. Penangan harus menganalisis kesalahan (bantuan kelas kesalahan khusus) dan menampilkan kembali kesalahan yang tidak diketahui (mungkin itu kesalahan pemrograman).
Tidak apa-apa untuk tidak menggunakan .catch
sama sekali, jika tidak ada cara untuk memulihkan kesalahan.
Bagaimanapun kita harus memiliki pengendali kejadian unhandledrejection
(untuk browser, dan analog untuk lingkungan lain) untuk melacak kesalahan yang tidak tertangani dan menginformasikannya kepada pengguna (dan mungkin server kita), sehingga aplikasi kita tidak pernah “mati begitu saja”.
Bagaimana menurutmu? Akankah .catch
terpicu? Jelaskan jawaban Anda.
Janji baru(fungsi(putuskan, tolak) { setWaktu habis(() => { melempar Kesalahan baru("Ups!"); }, 1000); }).menangkap(waspada);
Jawabannya adalah: tidak, tidak akan :
Janji baru(fungsi(putuskan, tolak) { setWaktu habis(() => { melempar Kesalahan baru("Ups!"); }, 1000); }).menangkap(waspada);
Seperti yang dikatakan dalam bab ini, ada “ try..catch
implisit” di sekitar kode fungsi. Jadi semua kesalahan sinkron ditangani.
Tapi di sini kesalahan terjadi bukan saat eksekutor sedang berjalan, tapi nanti. Jadi janjinya tidak bisa mengatasinya.