Skip to content

Error handling with promises #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Oct 7, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
The answer is: **no, it won't**:
La réponse est: **Non, cela n'arrivera pas:**:

```js run
new Promise(function(resolve, reject) {
Expand All @@ -8,6 +8,6 @@ new Promise(function(resolve, reject) {
}).catch(alert);
```

As said in the chapter, there's an "implicit `try..catch`" around the function code. So all synchronous errors are handled.
Comme décrit dans le chapitre, il y a un "`try..catch` implicite" autour du code de la fonction. Toutes les erreurs synchrones sont donc traitées.

But here the error is generated not while the executor is running, but later. So the promise can't handle it.
Mais ici, l'erreur n'est pas générée pendant l'exécution de l'exécuteur, mais plus tard. Donc la promesse ne peut pas tenir.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Error in setTimeout
# Erreur dans setTimeout

What do you think? Will the `.catch` trigger? Explain your answer.
Qu'en pensez-vous ? Est-ce que le `.catch` va se déclencher ? Expliquez votre réponse.

```js
new Promise(function(resolve, reject) {
Expand Down
104 changes: 52 additions & 52 deletions 1-js/11-async/04-promise-error-handling/article.md
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@

# Error handling with promises
# Gestion des erreurs avec des promesses

Promise chains are great at error handling. When a promise rejects, the control jumps to the closest rejection handler. That's very convenient in practice.
Les chaînes de promesses sont excellentes pour la gestion des erreurs. Lorsqu'une promesse est rejetée, le contrôle saute au gestionnaire de rejet le plus proche. C'est très pratique en pratique.

For instance, in the code below the URL to `fetch` is wrong (no such site) and `.catch` handles the error:
Par exemple, dans le code en dessous de l'URL de `fetch` est faux (aucun site de ce type) et `.catch` gère l'erreur :

```js run
*!*
fetch('https://no-such-server.blabla') // rejects
fetch('https://no-such-server.blabla') // rejets
*/!*
.then(response => response.json())
.catch(err => alert(err)) // TypeError: failed to fetch (the text may vary)
.catch(err => alert(err)) // TypeError: failed to fetch (le texte peut varier)
```

As you can see, the `.catch` doesn't have to be immediate. It may appear after one or maybe several `.then`.
Comme vous pouvez le voir, le `.catch` n'a pas besoin d'être immédiat. Il peut apparaître après un ou peut-être plusieurs `.then`.

Or, maybe, everything is all right with the site, but the response is not valid JSON. The easiest way to catch all errors is to append `.catch` to the end of chain:
Ou, peut-être, que tout va bien avec le site, mais la réponse JSON n'est pas valide. La façon la plus simple d'attraper toutes les erreurs est d'ajouter `.catch` à la fin de la chaîne :

```js run
fetch('/article/promise-chaining/user.json')
Expand All @@ -38,13 +38,13 @@ fetch('/article/promise-chaining/user.json')
*/!*
```

Normally, such `.catch` doesn't trigger at all. But if any of the promises above rejects (a network problem or invalid json or whatever), then it would catch it.
Normalement, un tel `.catch` ne se déclenche pas du tout. Mais si l'une des promesses ci-dessus rejette (un problème de réseau, un json invalide ou autre), alors il l'attraperait.

## Implicit try..catch
## try..catch implicite

The code of a promise executor and promise handlers has an "invisible `try..catch`" around it. If an exception happens, it gets caught and treated as a rejection.
Le code d'un exécuteur de promesses et d'un gestionnaire de promesses est entouré d'un "`try...catch` invisible". Si une exception se produit, elle est prise en compte et traitée comme un rejet.

For instance, this code:
Par exemple, ce code:

```js run
new Promise((resolve, reject) => {
Expand All @@ -54,7 +54,7 @@ new Promise((resolve, reject) => {
}).catch(alert); // Error: Whoops!
```

...Works exactly the same as this:
...Fonctionne exactement de la même façon que ceci:

```js run
new Promise((resolve, reject) => {
Expand All @@ -64,48 +64,48 @@ new Promise((resolve, reject) => {
}).catch(alert); // Error: Whoops!
```

The "invisible `try..catch`" around the executor automatically catches the error and turns it into rejected promise.
Le "`try..catch` invisible" autour de l'exécuteur attrape automatiquement l'erreur et la transforme en promesse rejetée.

This happens not only in the executor function, but in its handlers as well. If we `throw` inside a `.then` handler, that means a rejected promise, so the control jumps to the nearest error handler.
Cela se produit non seulement dans la fonction exécuteur, mais aussi dans ses gestionnaires. Si nous utilisons `throw` à l'intérieur d'un gestionnaire `.then', cela signifie une promesse rejetée, donc le contrôle saute au gestionnaire d'erreur le plus proche.

Here's an example:
En voici un exemple:

```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
throw new Error("Whoops!"); // rejects the promise
throw new Error("Whoops!"); // rejette la promesse
*/!*
}).catch(alert); // Error: Whoops!
```

This happens for all errors, not just those caused by the `throw` statement. For example, a programming error:
Cela se produit pour toutes les erreurs, pas seulement celles causées par l'état `throw`. Par exemple, une erreur de programmation :

```js run
new Promise((resolve, reject) => {
resolve("ok");
}).then((result) => {
*!*
blabla(); // no such function
blabla(); // aucune fonction de ce type
*/!*
}).catch(alert); // ReferenceError: blabla is not defined
```

The final `.catch` not only catches explicit rejections, but also occasional errors in the handlers above.
Le `.catch` final n'attrape pas seulement les rejets explicites, mais aussi les erreurs occasionnelles dans les gestionnaires ci-dessus.

## Rethrowing
## Renouvellement

As we already noticed, `.catch` at the end of the chain is similar to `try..catch`. We may have as many `.then` handlers as we want, and then use a single `.catch` at the end to handle errors in all of them.
Comme nous l'avons déjà remarqué, `.catch` à la fin de la chaîne est similaire à `try...catch`. Nous pouvons avoir autant de gestionnaires `.then` que nous le voulons, puis utiliser un seul `.catch` à la fin pour gérer les erreurs dans chacun d'eux.

In a regular `try..catch` we can analyze the error and maybe rethrow it if can't handle. The same thing is possible for promises.
Dans un `try...catch` régulier nous pouvons analyser l'erreur et peut-être la relancer si nous ne pouvons pas la gérer. La même chose est possible pour les promesses.

If we `throw` inside `.catch`, then the control goes to the next closest error handler. And if we handle the error and finish normally, then it continues to the closest successful `.then` handler.
Si nous utilisons`throw` dans `.catch`, alors le contrôle passe au gestionnaire d'erreur suivant qui est plus proche. Et si nous gérons l'erreur et finissons normalement, alors elle continue jusqu'au gestionnaire `.then` le plus proche.

In the example below the `.catch` successfully handles the error:

```js run
// the execution: catch -> then
// l'exécution: catch -> then
new Promise((resolve, reject) => {

throw new Error("Whoops!");
Expand All @@ -117,12 +117,12 @@ new Promise((resolve, reject) => {
}).then(() => alert("Next successful handler runs"));
```

Here the `.catch` block finishes normally. So the next successful `.then` handler is called.
Ici, le bloc `.catch` se termine normalement. Le prochain gestionnaire `.then` réussi est donc appelé.

In the example below we see the other situation with `.catch`. The handler `(*)` catches the error and just can't handle it (e.g. it only knows how to handle `URIError`), so it throws it again:
Dans l'exemple ci-dessous nous voyons l'autre situation avec `.catch`. Le gestionnaire `(*)` attrape l'erreur et ne peut tout simplement pas la gérer (par ex: il sait seulement comment gérer `URIError`), donc il la relance:

```js run
// the execution: catch -> catch -> then
// l'exécution: catch -> catch -> then
new Promise((resolve, reject) => {

throw new Error("Whoops!");
Expand All @@ -135,51 +135,51 @@ new Promise((resolve, reject) => {
alert("Can't handle such error");

*!*
throw error; // throwing this or another error jumps to the next catch
throw error; // lancer cette erreur ou une autre saute au prochain catch.
*/!*
}

}).then(function() {
/* doesn't run here */
/* ne s'exécute pas ici */
}).catch(error => { // (**)

alert(`The unknown error has occurred: ${error}`);
// don't return anything => execution goes the normal way
// ne retourne rien => l'exécution se déroule normalement

});
```

The execution jumps from the first `.catch` `(*)` to the next one `(**)` down the chain.

## Unhandled rejections
## Rejets non traités

What happens when an error is not handled? For instance, we forgot to append `.catch` to the end of the chain, like here:
Que se passe-t-il lorsqu'une erreur n'est pas traitée ? Par exemple, nous avons oublié d'ajouter `.catch` à la fin de la chaîne, comme ici :

```js untrusted run refresh
new Promise(function() {
noSuchFunction(); // Error here (no such function)
noSuchFunction(); // Erreur ici (aucune fonction de ce type)
})
.then(() => {
// successful promise handlers, one or more
}); // without .catch at the end!
// gestionnaires de promesses réussit, une ou plus
}); // sans .catch à la fin!
```

In case of an error, the promise becomes rejected, and the execution should jump to the closest rejection handler. But there is none. So the error gets "stuck". There's no code to handle it.
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 est donc "coincée". Il n'y a pas de code pour le gérer.

In practice, just like with regular unhandled errors in code, it means that something has terribly gone wrong.
En pratique, tout comme pour les erreurs régulières qui sont non gérées dans le code, cela signifie que quelque chose a très mal tourné.

What happens when a regular error occurs and is not caught by `try..catch`? The script dies with a message in console. Similar thing happens with unhandled promise rejections.
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. Il se produit la même chose lors du rejet de promesses non tenues.

JavaScript engine tracks such rejections and generates a global error in that case. You can see it in the console if you run the example above.
Le moteur JavaScript suit ces rejets et génère une erreur globale dans ce cas. Vous pouvez le voir dans la console si vous exécutez l'exemple ci-dessus.

In the browser we can catch such errors using the event `unhandledrejection`:
Dans le navigateur, nous pouvons détecter de telles erreurs en utilisant l'événement `unhandledrejection`:

```js run
*!*
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
// l'objet event possède deux propriétés spéciales:
alert(event.promise); // [object Promise] - la promesse qui a généré l'erreur
alert(event.reason); // Error: Whoops! - l'objet d'erreur non géré
});
*/!*

Expand All @@ -188,17 +188,17 @@ new Promise(function() {
}); // no catch to handle the error
```

The event is the part of the [HTML standard](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections).
L'événement fait partie des [standards HTML](https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections) *(en anglais)*.

If an error occurs, and there's no `.catch`, the `unhandledrejection` handler triggers, and gets the `event` object with the information about the error, so we can do something.
Si une erreur se produit, et qu'il n'y a pas de `.catch`, le gestionnaire `unhandledrejection` se déclenche, et reçoit l'objet `event` avec les informations sur l'erreur, donc nous pouvons faire quelque chose.

Usually such errors are unrecoverable, so our best way out is to inform the user about the problem and probably report the incident to the server.
Habituellement, de telles erreurs sont irrécupérables, donc notre meilleure solution est d'informer l'utilisateur à propos du problème et probablement de signaler l'incident au serveur.

In non-browser environments like Node.js there are other ways to track unhandled errors.
Dans les environnements sans navigateur comme Node.js, il existe d'autres moyens de suivre les erreurs non gérées.

## Summary
## Résumé

- `.catch` handles errors in promises of all kinds: be it a `reject()` call, or an error thrown in a handler.
- We should place `.catch` exactly in places where we want to handle errors and know how to handle them. The handler should analyze errors (custom error classes help) and rethrow unknown ones (maybe they are programming mistakes).
- It's ok not to use `.catch` at all, if there's no way to recover from an error.
- In any case we should have the `unhandledrejection` event handler (for browsers, and analogs for other environments), to track unhandled errors and inform the user (and probably our server) about the them, so that our app never "just dies".
- `.catch` gère les erreurs dans les promesses de toutes sortes : qu'il s'agisse d'un appel `reject()', ou d'une erreur lancée dans un gestionnaire.
- Nous devrions placer `.catch' exactement aux endroits où nous voulons traiter les erreurs et savoir comment les traiter. Le gestionnaire doit analyser les erreurs (les classes d'erreurs personnalisées aident) et relancer les erreurs inconnues (ce sont peut-être des erreurs de programmation).
- C'est acceptable de ne pas utiliser `.catch` du tout, 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 les analogues pour les autres environnements), pour suivre les erreurs non gérées et informer l'utilisateur (et probablement notre serveur) à leur sujet, afin que notre application ne "meurt jamais".