Promise chains jsou skvělé při řešení chyb. Když je příslib odmítnut, kontrola přeskočí na nejbližší obslužný program odmítnutí. To je v praxi velmi pohodlné.
Například v kódu pod adresou URL fetch
je chybný (žádný takový web) a .catch
zpracovává chybu:
fetch('https://no-such-server.blabla') // rejects
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
Jak můžete vidět, .catch
nemusí být bezprostřední. Může se objevit po jednom nebo možná po několika .then
.
Nebo je možná s webem vše v pořádku, ale odpověď není platný JSON. Nejjednodušší způsob, jak zachytit všechny chyby, je připojit .catch
na konec řetězce:
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));
Obvykle je to .catch
vůbec nespouští. Ale pokud některý z výše uvedených příslibů odmítne (problém se sítí nebo neplatný json nebo cokoli jiného), pak to zachytí.
Implicitní pokus…catch
Kód vykonavatele slibu a obsluhy slibu má „neviditelné try..catch
Pokud dojde k výjimce, bude zachycena a považována za odmítnutí.
Například tento kód:
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(alert); // Error: Whoops!
…Funguje přesně stejně jako toto:
new Promise((resolve, reject) => {
reject(new Error("Whoops!"));
}).catch(alert); // Error: Whoops!
"Neviditelný try..catch
" kolem exekutor automaticky zachytí chybu a změní ji na odmítnutý slib.
To se děje nejen ve funkci exekutora, ale také v jeho obsluhách. Pokud throw
uvnitř .then
handler, to znamená odmítnutý příslib, takže ovládací prvek skočí na nejbližší handler chyb.
Zde je příklad:
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
throw new Error("Whoops!"); // rejects the promise
}).catch(alert); // Error: Whoops!
K tomu dochází u všech chyb, nejen těch způsobených throw
tvrzení. Například chyba programování:
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
blabla(); // no such function
}).catch(alert); // ReferenceError: blabla is not defined
Poslední .catch
nejen zachytí explicitní odmítnutí, ale také náhodné chyby ve výše uvedených obslužných rutinách.
Obnovení
Jak jsme si již všimli, .catch
na konci řetězce je podobný try..catch
. Můžeme mít tolik .then
handlery, jak chceme, a pak použijte jeden .catch
na konci zvládnout chyby ve všech z nich.
V běžném try..catch
můžeme chybu analyzovat a možná ji vrátit, pokud se s ní nedá zacházet. Totéž je možné u slibů.
Pokud throw
uvnitř .catch
, pak ovládací prvek přejde na další nejbližší obslužnou rutinu chyb. A pokud chybu vyřešíme a dokončíme normálně, pokračuje k dalšímu nejbližšímu úspěšnému .then
handler.
V níže uvedeném příkladu .catch
úspěšně zpracuje chybu:
// the execution: 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"));
Zde je .catch
blok skončí normálně. Takže další úspěšný .then
se nazývá handler.
V příkladu níže vidíme jinou situaci s .catch
. Obslužná rutina (*)
zachytí chybu a prostě ji nezvládne (např. ví jen, jak zpracovat URIError
), takže to hodí znovu:
// the execution: catch -> catch
new Promise((resolve, reject) => {
throw new Error("Whoops!");
}).catch(function(error) { // (*)
if (error instanceof URIError) {
// handle it
} else {
alert("Can't handle such error");
throw error; // throwing this or another error jumps to the next catch
}
}).then(function() {
/* doesn't run here */
}).catch(error => { // (**)
alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
});
Provedení skočí z prvního .catch
(*)
na další (**)
v řetězci.
Nevyřízená odmítnutí
Co se stane, když se chyba neošetří? Například jsme zapomněli připojit .catch
na konec řetězce, jako zde:
new Promise(function() {
noSuchFunction(); // Error here (no such function)
})
.then(() => {
// successful promise handlers, one or more
}); // without .catch at the end!
V případě chyby se příslib stane odmítnutým a provedení by mělo přejít na nejbližší obslužnou osobu pro odmítnutí. Ale žádná není. Chyba se tedy „zasekne“. Neexistuje žádný kód, který by to zvládl.
V praxi, stejně jako u běžných neošetřených chyb v kódu, to znamená, že se něco strašně pokazilo.
Co se stane, když dojde k běžné chybě a není zachycena try..catch
? Skript zemře se zprávou v konzole. Podobná věc se stane s neošetřeným odmítnutím slibu.
JavaScript engine sleduje taková odmítnutí a v takovém případě vygeneruje globální chybu. Můžete to vidět v konzole, pokud spustíte příklad výše.
V prohlížeči můžeme takové chyby zachytit pomocí události unhandledrejection
:
window.addEventListener('unhandledrejection', function(event) {
// the event object has two special properties:
alert(event.promise); // [object Promise] - the promise that generated the error
alert(event.reason); // Error: Whoops! - the unhandled error object
});
new Promise(function() {
throw new Error("Whoops!");
}); // no catch to handle the error
Událost je součástí standardu HTML.
Pokud dojde k chybě a není zde žádné .catch
, unhandledrejection
handler spustí a získá event
namítněte s informací o chybě, abychom mohli něco udělat.
Obvykle jsou takové chyby neodstranitelné, takže naším nejlepším způsobem je informovat uživatele o problému a pravděpodobně incident nahlásit serveru.
V prostředích bez prohlížeče, jako je Node.js, existují další způsoby, jak sledovat neošetřené chyby.
Shrnutí
.catch
zpracovává chyby ve slibech všeho druhu:ať už je toreject()
volání nebo chyba vyvolaná v handleru..then
také zachytí chyby stejným způsobem, pokud je uveden druhý argument (což je obsluha chyb).- Měli bychom umístit
.catch
přesně v místech, kde si chceme poradit s chybami a víme, jak s nimi zacházet. Obslužná rutina by měla analyzovat chyby (pomocí vlastních tříd chyb) a vrátit neznámé (možná jde o chyby v programování). - Není v pořádku nepoužívat
.catch
vůbec, pokud neexistuje způsob, jak se z chyby zotavit. - V každém případě bychom měli mít
unhandledrejection
obslužný program událostí (pro prohlížeče a analogy pro jiná prostředí), který sleduje neošetřené chyby a informuje o nich uživatele (a pravděpodobně i náš server), aby naše aplikace nikdy „neumřela“.