Ošetření chyb se sliby

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 to reject() 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“.