Les chaînes de promesses sont excellentes pour la gestion des erreurs. Lorsqu'une promesse est rejetée, le contrôle passe au gestionnaire de rejet le plus proche. C'est très pratique en pratique.
Par exemple, dans le code ci-dessous, l'URL à fetch
est erronée (aucun site de ce type) et .catch
gère l'erreur :
fetch('https://no-such-server.blabla') // rejette .then(réponse => réponse.json()) .catch(err => alert(err)) // TypeError : échec de la récupération (le texte peut varier)
Comme vous pouvez le constater, le .catch
ne doit pas nécessairement être immédiat. Il peut apparaître après un ou plusieurs .then
.
Ou, peut-être, tout va bien avec le site, mais la réponse n'est pas un JSON valide. Le moyen le plus simple de détecter toutes les erreurs consiste à ajouter .catch
à la fin de la chaîne :
récupérer('https://javascript.info/article/promise-chaining/user.json') .then(réponse => réponse.json()) .then(user => fetch(`https://api.github.com/users/${user.name}`)) .then(réponse => réponse.json()) .then(githubUser => new Promise((résoudre, rejeter) => { let img = document.createElement('img'); img.src = githubUser.avatar_url; img.className = "promesse-avatar-exemple"; document.body.append(img); setTimeout(() => { img.remove(); résoudre (githubUser); }, 3000); })) .catch(erreur => alerte(erreur.message));
Normalement, un tel .catch
ne se déclenche pas du tout. Mais si l'une des promesses ci-dessus est rejetée (un problème de réseau ou un json invalide ou autre), alors elle l'attrapera.
Le code d'un exécuteur de promesse et de gestionnaires de promesses est entouré d'un « try..catch
invisible ». Si une exception se produit, elle est détectée et traitée comme un rejet.
Par exemple, ce code :
nouvelle promesse ((résoudre, rejeter) => { throw new Error("Oups!"); }).catch(alerte); // Erreur : Oups !
…Fonctionne exactement de la même manière que ceci :
nouvelle promesse ((résoudre, rejeter) => { rejeter(nouvelle erreur("Oups !")); }).catch(alerte); // Erreur : Oups !
Le « try..catch
invisible » autour de l’exécuteur détecte automatiquement l’erreur et la transforme en promesse rejetée.
Cela se produit non seulement dans la fonction d'exécuteur, mais également dans ses gestionnaires. Si nous throw
à l'intérieur d'un gestionnaire .then
, cela signifie une promesse rejetée, donc le contrôle passe au gestionnaire d'erreurs le plus proche.
Voici un exemple :
nouvelle promesse ((résoudre, rejeter) => { résoudre("ok"); }).then((résultat) => { throw new Error("Oups!"); // rejette la promesse }).catch(alerte); // Erreur : Oups !
Cela se produit pour toutes les erreurs, pas seulement celles provoquées par l'instruction throw
. Par exemple, une erreur de programmation :
nouvelle promesse ((résoudre, rejeter) => { résoudre("ok"); }).then((résultat) => { blabla(); // aucune fonction de ce type }).catch(alerte); // ReferenceError : blabla n'est pas défini
Le .catch
final détecte non seulement les rejets explicites, mais également les erreurs accidentelles dans les gestionnaires ci-dessus.
Comme nous l'avons déjà remarqué, .catch
en fin de chaîne est similaire à try..catch
. Nous pouvons avoir autant de gestionnaires .then
que nous le souhaitons, puis utiliser un seul .catch
à la fin pour gérer les erreurs dans chacun d'eux.
Lors d'un try..catch
régulier, nous pouvons analyser l'erreur et peut-être la renvoyer si elle ne peut pas être gérée. La même chose est possible pour les promesses.
Si nous throw
à l'intérieur .catch
, alors le contrôle passe au gestionnaire d'erreurs le plus proche. Et si nous traitons l'erreur et terminons normalement, alors elle passe au prochain gestionnaire .then
réussi le plus proche.
Dans l'exemple ci-dessous, le .catch
gère avec succès l'erreur :
// l'exécution : catch -> then nouvelle promesse ((résoudre, rejeter) => { throw new Error("Oups!"); }).catch(fonction(erreur) { alert("L'erreur est gérée, continuez normalement"); }).then(() => alert("Prochaine exécution réussie du gestionnaire"));
Ici, le bloc .catch
se termine normalement. Ainsi, le prochain gestionnaire .then
réussi est appelé.
Dans l'exemple ci-dessous, nous voyons l'autre situation avec .catch
. Le gestionnaire (*)
détecte l'erreur et ne peut tout simplement pas la gérer (par exemple, il sait seulement comment gérer URIError
), donc il la relance :
// l'exécution : catch -> catch nouvelle promesse ((résoudre, rejeter) => { throw new Error("Oups!"); }).catch(fonction(erreur) { // (*) if (instance d'erreur d'URIerror) { // je m'en occupe } autre { alert("Impossible de gérer une telle erreur"); erreur de lancement ; // lancer telle ou telle erreur passe à la capture suivante } }).then(function() { /* ne fonctionne pas ici */ }).catch(erreur => { // (**) alert(`Une erreur inconnue s'est produite : ${error}`); // ne renvoie rien => l'exécution se déroule normalement });
L'exécution passe du premier .catch
(*)
au suivant (**)
dans la chaîne.
Que se passe-t-il lorsqu'une erreur n'est pas gérée ? Par exemple, nous avons oublié d'ajouter .catch
à la fin de la chaîne, comme ici :
nouvelle promesse (fonction () { noSuchFunction(); // Erreur ici (aucune fonction de ce type) }) .puis(() => { // gestionnaires de promesses réussis, un ou plusieurs }); // sans .catch à la fin !
En cas d'erreur, la promesse est rejetée et l'exécution doit passer au gestionnaire de rejet le plus proche. Mais il n’y en a pas. L'erreur reste donc "bloquée". Il n'y a pas de code pour le gérer.
En pratique, tout comme pour les erreurs de code non gérées, cela signifie que quelque chose s’est terriblement mal passé.
Que se passe-t-il lorsqu'une erreur régulière se produit et n'est pas détectée par try..catch
? Le script meurt avec un message dans la console. Une chose similaire se produit avec les refus de promesses non gérés.
Le moteur JavaScript suit ces refus et génère dans ce cas une erreur globale. Vous pouvez le voir dans la console si vous exécutez l'exemple ci-dessus.
Dans le navigateur, nous pouvons détecter de telles erreurs en utilisant l'événement unhandledrejection
:
window.addEventListener('unhandledrejection', function(event) { // l'objet événement a deux propriétés spéciales : alert(event.promise); // [object Promise] - la promesse qui a généré l'erreur alerte(événement.raison); // Erreur : Oups ! - l'objet erreur non géré }); nouvelle promesse (fonction () { throw new Error("Oups!"); }); // pas de catch pour gérer l'erreur
L'événement fait partie du standard HTML.
Si une erreur se produit et qu'il n'y a pas de .catch
, le gestionnaire unhandledrejection
se déclenche et récupère l'objet event
avec les informations sur l'erreur, afin que nous puissions faire quelque chose.
Habituellement, ces erreurs sont irrécupérables, notre meilleure solution est donc d'informer l'utilisateur du problème et probablement de signaler l'incident au serveur.
Dans les environnements sans navigateur comme Node.js, il existe d'autres moyens de suivre les erreurs non gérées.
.catch
gère les erreurs dans les promesses de toutes sortes : qu'il s'agisse d'un appel reject()
ou d'une erreur générée dans un gestionnaire.
.then
détecte également les erreurs de la même manière, si le deuxième argument (qui est le gestionnaire d’erreurs) est fourni.
Nous devons placer .catch
exactement aux endroits où nous voulons gérer les erreurs et savoir comment les gérer. Le gestionnaire doit analyser les erreurs (aide des classes d'erreurs personnalisées) et renvoyer les erreurs inconnues (il s'agit peut-être d'erreurs de programmation).
Vous pouvez ne pas utiliser du tout .catch
s'il n'y a aucun moyen de récupérer d'une erreur.
Dans tous les cas, nous devrions avoir le gestionnaire d'événements unhandledrejection
(pour les navigateurs et analogues pour d'autres environnements) pour suivre les erreurs non gérées et en informer l'utilisateur (et probablement notre serveur), afin que notre application ne « meure jamais ».
Qu'en penses-tu? Le .catch
se déclenchera-t-il ? Expliquez votre réponse.
nouvelle promesse (fonction (résoudre, rejeter) { setTimeout(() => { throw new Error("Oups!"); }, 1000); }).catch(alerte);
La réponse est : non, ce ne sera pas le cas :
nouvelle promesse (fonction (résoudre, rejeter) { setTimeout(() => { throw new Error("Oups!"); }, 1000); }).catch(alerte);
Comme dit dans le chapitre, il y a un « try..catch
implicite » autour du code de la fonction. Ainsi, toutes les erreurs synchrones sont gérées.
Mais ici, l'erreur n'est pas générée pendant l'exécution de l'exécuteur, mais plus tard. La promesse ne peut donc pas le gérer.