Skip to content

Commit

Permalink
Translation of returningpromises.md (#14)
Browse files Browse the repository at this point in the history
  • Loading branch information
forresst authored Jan 18, 2021
1 parent be6bf84 commit adc053e
Showing 1 changed file with 98 additions and 98 deletions.
196 changes: 98 additions & 98 deletions sections/errorhandling/returningpromises.french.md
Original file line number Diff line number Diff line change
@@ -1,64 +1,64 @@
# Returning promises
# Le retour des promesses

<br/>

### One Paragraph Explainer
### Un paragraphe d'explication

When an error occurs, whether from a synchronous or asynchronous flow, it's imperative to have a full stacktrace of the error flow. Surprisingly, if an async function returns a promise (e.g., calls other async function) without awaiting, should an error occur then the caller function won't appear in the stacktrace. This will leave the person who diagnoses the error with partial information - All the more if the error cause lies within that caller function. There is a feature v8 called "zero-cost async stacktraces" that allow stacktraces not to be cut on the most recent `await`. But due to non-trivial implementation details, it will not work if the return value of a function (sync or async) is a promise. So, to avoid holes in stacktraces when returned promises would be rejected, we must always explicitly resolve promises with `await` before returning them from functions
Lorsqu'une erreur se produit, qu'elle provienne d'un flux synchrone ou asynchrone, il est impératif de disposer d'une trace de pile complète du flux d'erreurs. Étonnamment, lorsqu'une fonction `async` renvoie une promesse sans `await` (par exemple, elle appelle une autre fonction `async`), si une erreur se produit, la fonction appelante n'apparaîtra pas dans la trace de la pile. La personne qui diagnostiquera l'erreur ne disposera donc que d'une information partielle - d'autant plus si la cause de l'erreur se situe dans cette fonction d'appel. Il existe une fonctionnalité v8 appelée "zero-cost async stacktraces" qui permet de ne pas couper les traces de pile sur les `await` les plus récents. Mais sans certaines modalités de mise en œuvre non négligeables, elle ne fonctionnera pas si la valeur de retour d'une fonction (sync ou async) est une promesse. Donc, pour éviter les trous dans les traces de pile lorsque des promesses retournées sont rejetées, nous devons toujours résoudre explicitement les promesses avec `await` avant de les retourner à partir des fonctions.

<br/>

### Code example Anti-Pattern: Calling async function without awaiting
### Exemple de code incorrect : appel d'une fonction async sans await

<details><summary>Javascript</summary>
<p>

```javascript
async function throwAsync(msg) {
await null // need to await at least something to be truly async (see note #2)
await null // il faut attendre au moins quelque chose pour être vraiment asynchrone (voir note 2)
throw Error(msg)
}

async function returnWithoutAwait () {
return throwAsync('missing returnWithoutAwait in the stacktrace')
return throwAsync('manque returnWithoutAwait dans la trace de pile')
}

// 👎 will NOT have returnWithoutAwait in the stacktrace
// 👎 N'aura PAS la fonction returnWithoutAwait dans la trace de pile
returnWithoutAwait().catch(console.log)
```

would log
log reçu

```
Error: missing returnWithoutAwait in the stacktrace
Error: manque returnWithoutAwait dans la trace de pile
at throwAsync ([...])
```
</p>
</details>

### Code example: Calling and awaiting as appropriate
### Exemple de code : appel d'une fonction async avec await approprié

<details><summary>Javascript</summary>
<p>

```javascript
async function throwAsync(msg) {
await null // need to await at least something to be truly async (see note #2)
await null // il faut attendre au moins quelque chose pour être vraiment asynchrone (voir note 2)
throw Error(msg)
}

async function returnWithAwait() {
return await throwAsync('with all frames present')
return await throwAsync('avec toutes les instructions présentes')
}

// 👍 will have returnWithAwait in the stacktrace
// 👍 aura la fonction returnWithoutAwait dans la trace de pile
returnWithAwait().catch(console.log)
```

would log
log reçu

```
Error: with all frames present
Error: avec toutes les instructions présentes
at throwAsync ([...])
at async returnWithAwait ([...])
```
Expand All @@ -68,15 +68,15 @@ Error: with all frames present

<br/>

### Code example Anti-Pattern: Returning a promise without tagging the function as async
### Exemple de code incorrect : retourner une promesse sans marquer la fonction comme async

<details><summary>Javascript</summary>
<p>

```javascript
async function throwAsync () {
await null // need to await at least something to be truly async (see note #2)
throw Error('missing syncFn in the stacktrace')
await null // il faut attendre au moins quelque chose pour être vraiment asynchrone (voir note 2)
throw Error('manque syncFn dans la trace de pile')
}

function syncFn () {
Expand All @@ -87,30 +87,30 @@ async function asyncFn () {
return await syncFn()
}

// 👎 syncFn would be missing in the stacktrace because it returns a promise while been sync
// 👎 syncFn manquera dans la trace de pile parce qu'elle renverra une promesse alors qu'elle est sync
asyncFn().catch(console.log)
```

would log
log reçu

```
Error: missing syncFn in the stacktrace
Error: manque syncFn dans la trace de pile
at throwAsync ([...])
at async asyncFn ([...])
```

</p>
</details>

### Code example: Tagging the function that returns a promise as async
### Exemple de code : marquer la fonction comme async car elle renvoie une promesse

<details><summary>Javascript</summary>
<p>

```javascript
async function throwAsync () {
await null // need to await at least something to be truly async (see note #2)
throw Error('with all frames present')
await null // il faut attendre au moins quelque chose pour être vraiment asynchrone (voir note 2)
throw Error('avec toutes les instructions présentes')
}

async function changedFromSyncToAsyncFn () {
Expand All @@ -121,14 +121,14 @@ async function asyncFn () {
return await changedFromSyncToAsyncFn()
}

// 👍 now changedFromSyncToAsyncFn would present in the stacktrace
// 👍 maintenant changedFromSyncToAsyncFn sera présent dans la trace de la pile
asyncFn().catch(console.log)
```

would log
log reçu

```
Error: with all frames present
Error: avec toutes les instructions présentes
at throwAsync ([...])
at changedFromSyncToAsyncFn ([...])
at async asyncFn ([...])
Expand All @@ -139,88 +139,88 @@ Error: with all frames present

</br>

### Code Example Anti-pattern #3: direct usage of async callback where sync callback is expected
### Exemple de code incorrect : utilisation directe du rappel async lorsque le rappel sync est attendu

<details><summary>Javascript</summary>
<p>

```javascript
async function getUser (id) {
await null
if (!id) throw Error('stacktrace is missing the place where getUser has been called')
if (!id) throw Error('la trace de pile n\'indique pas l\'endroit où getUser a été appelé')
return {id}
}

const userIds = [1, 2, 0, 3]

// 👎 the stacktrace would include getUser function but would give no clue on where it has been called
// 👎 la trace de pile aura la fonction getUser mais ne donnera aucune indication sur l'endroit où elle a été appelée
Promise.all(userIds.map(getUser)).catch(console.log)
```

would log
log reçu

```
Error: stacktrace is missing the place where getUser has been called
Error: la trace de pile n'indique pas l'endroit où getUser a été appelé
at getUser ([...])
at async Promise.all (index 2)
```

*Side-note*: it may looks like `Promise.all (index 2)` can help understanding the place where `getUser` has been called,
but due to a [completely different bug in v8](https://bugs.chromium.org/p/v8/issues/detail?id=9023), `(index 2)` is
a line from internals of v8
*Remarque complémentaire* : on pourrait croire que `Promise.all (index 2)` peut aider à comprendre l'endroit où `getUser` a été appelé,
mais en raison d'un [bogue complètement différent dans la v8](https://bugs.chromium.org/p/v8/issues/detail?id=9023), `(index 2)` est
une ligne interne de v8

</p>
</details>

### Code example: wrap async callback in a dummy async function before passing it as a sync callback
### Exemple de code : envelopper le rappel async dans une fonction async factice avant de le passer en rappel sync

<details><summary>Javascript</summary>
<p>

*Note 1*: in case if you control the code of the function that would call the callback - just change that function to
async and add `await` before the callback call. Below I assume that you are not in charge of the code that is calling
the callback (or it's change is unacceptable for example because of backward compatibility)
*Remarque 1* : si vous contrôlez le code de la fonction qui appelle le rappel, changez simplement cette fonction
en async et ajoutez `await` avant l'appel du rappel. Ci-dessous, je suppose que vous n'êtes pas en charge du code qui appelle
le rappel (ou que son changement est inacceptable, par exemple en raison de la rétrocompatibilité)

*Note 2*: quite often usage of async callback in places where sync one is expected would not work at all. This is not about
how to fix the code that is not working - it's about how to fix stacktrace in case if code is already working as
expected
*Remarque 2* : très souvent, l'utilisation du rappel async dans les endroits où l'on s'attend à ce qu'il soit sync ne fonctionnerait pas du tout. Il ne s'agit pas
de savoir comment réparer le code qui ne fonctionne pas - il s'agit de savoir comment réparer la trace de pile au cas où le code fonctionne déjà
comme prévu

```javascript
async function getUser (id) {
await null
if (!id) throw Error('with all frames present')
if (!id) throw Error('avec toutes les instructions présentes')
return {id}
}

const userIds = [1, 2, 0, 3]

// 👍 now the line below is in the stacktrace
// 👍 maintenant la ligne ci-dessous est dans la trace de la pile
Promise.all(userIds.map(async id => await getUser(id))).catch(console.log)
```

would log
log reçu

```
Error: with all frames present
Error: avec toutes les instructions présentes
at getUser ([...])
at async ([...])
at async Promise.all (index 2)
```

where thanks to explicit `await` in `map`, the end of the line `at async ([...])` would point to the exact place where
`getUser` has been called
grâce au `await` explicite dans `map`, la fin de la ligne `at async ([...])` indique l'endroit exact
`getUser` est appelé

*Side-note*: if async function that wrap `getUser` would miss `await` before return (anti-pattern #1 + anti-pattern #3)
then only one frame would left in the stacktrace:
*Remarque complémentaire* : si la fonction async qui enveloppe `getUser` n'a pas `await` avant le retour (exemple incorrect n°1 + exemple incorrect n°3)
alors il ne restera qu'une seule instruction dans la trace de la pile :

```javascript
[...]

// 👎 anti-pattern 1 + anti-pattern 3 - only one frame left in stacktrace
// 👎 exemple incorrect n°1 + exemple incorrect n°3 - une seule instruction dans la trace de la pile
Promise.all(userIds.map(async id => getUser(id))).catch(console.log)
```

would log
log reçu

```
Error: [...]
Expand All @@ -232,54 +232,54 @@ Error: [...]

<br/>

## Advanced explanation

The mechanisms behind sync functions stacktraces and async functions stacktraces in v8 implementation are quite different:
sync stacktrace is based on **stack** provided by operating system Node.js is running on (just like in most programming
languages). When an async function is executing, the **stack** of operating system is popping it out as soon as the
function is getting to it's first `await`. So async stacktrace is a mix of operating system **stack** and a rejected
**promise resolution chain**. Zero-cost async stacktraces implementation is extending the **promise resolution chain**
only when the promise is getting `awaited` <span>[¹](#1)</span>. Because only `async` functions may `await`,
sync function would always be missed in async stacktrace if any async operation has been performed after the function
has been called <span>[²](#2)</span>

### The tradeoff

Every `await` creates a new microtask in the event loop, so adding more `await`s to the code would
introduce some performance penalty. Nevertheless, the performance penalty introduced by network or
database is [tremendously larger](https://colin-scott.github.io/personal_website/research/interactive_latency.html)
so additional `await`s penalty is not something that should be considered during web servers or CLI
development unless for a very hot code per request or command. So removing `await`s in
`return await`s should be one of the last places to search for noticeable performance boost and
definitely should never be done up-front


### Why return await was considered as anti-pattern in the past

There is a number of [excellent articles](https://jakearchibald.com/2017/await-vs-return-vs-return-await/) explained
why `return await` should never be used outside of `try` block and even an
[ESLint rule](https://eslint.org/docs/rules/no-return-await) that disallows it. The reason for that is the fact that
since async/await become available with transpilers in Node.js 0.10 (and got native support in Node.js 7.6) and until
"zero-cost async stacktraces" was introduced in Node.js 10 and unflagged in Node.js 12, `return await` was absolutely
equivalent to `return` for any code outside of `try` block. It may still be the same for some other ES engines. This
is why resolving promises before returning them is the best practice for Node.js and not for the EcmaScript in general

### Notes:

1. One another reason why async stacktrace has such tricky implementation is the limitation that stacktrace
must always be built synchronously, on the same tick of event loop <span id="a1">[¹](#1)</span>
2. Without `await` in `throwAsync` the code would be executed in the same phase of event loop. This is a
degenerated case when OS **stack** would not get empty and stacktrace be full even without explicitly
awaiting the function result. Usually usage of promises include some async operations and so parts of
the stacktrace would get lost
3. Zero-cost async stacktraces still would not work for complicated promise usages e.g. single promise
awaited many times in different places

### References:
<span id="1">1. </span>[Blog post on zero-cost async stacktraces in v8](https://v8.dev/blog/fast-async)
## Explication approfondie

Les mécanismes des traces de piles des fonctions de sync et des fonctions async dans l'implémentation de la v8 sont très différents :
La trace de pile sync est basée sur la **pile** fournie par le système d'exploitation sur lequel tourne Node.js (comme dans la plupart des langages
de programmation). Lorsqu'une fonction async est en cours d'exécution, la **pile** du système d'exploitation l'a sort dès que la
fonction est arrivée à son premier `await`. Donc la trace de pile async est un mélange de la **pile** du système d'exploitation et d'une
**chaîne de résolution des promesses** rejetées. L'implémentation "zero-cost async stacktraces" étend la **chaîne de résolution des promesses**
uniquement lorsque la promesse est `awaited` <span>[¹](#1)</span>. Parce que seules les fonctions `async` peuvent `await`,
la fonction sync sera toujours manquante dans la trace de la pile async si une opération async a été effectuée après que la fonction
a été appelé <span>[²](#2)</span>

### Le compromis

Chaque `await` crée une nouvelle micro-tâche dans la boucle d'événement, donc l'ajout d'autres `await` dans le code
introduit une certaine pénalité de performance. Néanmoins, la pénalité de performance introduite par le réseau ou
la base de données est [énormément plus grande](https://colin-scott.github.io/personal_website/research/interactive_latency.html)
donc la pénalité supplémentaire `await` n'est pas quelque chose qui devrait être considéré pendant le développement de serveurs web ou de CLI
sauf pour un code très chaud par requête ou commande. Donc, la suppression de `await` dans
les `return await` devrait être l'un des derniers moyens pour améliorer sensiblement les performances
et ne doit absolument pas être fait en amont.


### Pourquoi return await était considéré comme incorrect dans le passé

Un certain nombre d'[excellents articles](https://jakearchibald.com/2017/await-vs-return-vs-return-await/) expliquent
pourquoi `return await` ne devrait jamais être utilisée en dehors du bloc `try` et même une
[règle ESLint](https://eslint.org/docs/rules/no-return-await) l'interdit. La raison, c'est que depuis que async/await
est disponible avec des transpileurs dans Node.js 0.10 (et a obtenu un support natif dans Node.js 7.6) et jusqu'à ce
que "zero-cost async stacktraces" a été introduit dans Node.js 10 et démarqué dans Node.js 12, `return await` était absolument
équivalent à `return` pour tout code en dehors du bloc `try`. Il se peut que ce soit encore le cas pour certains autres moteurs ES.
C'est pourquoi la résolution des promesses avant de les retourner est la meilleure pratique pour Node.js et non pour EcmaScript en général

### Remarques :

1. Une autre raison pour laquelle la trace de pile async a une implémentation aussi délicate, c'est que la trace de pile
doit toujours être construite de manière synchrone, sur le même rythme que la boucle d'événement <span id="a1">[¹](#1)</span>
2. Sans `await` dans `throwAsync`, le code serait exécuté dans la même phase de la boucle d'événements. C'est un cas
dégradé où la **pile** de l'OS ne serait pas vide et la trace de pile serait pleine même sans attendre
explicitement le résultat de la fonction. Habituellement, l'utilisation des promesses inclut des opérations asynchrones
et des parties de la trace de la pile sont perdues
3. Zero-cost async stacktraces ne fonctionnera toujours pas pour les usages compliqués de la promesse, par exemple la promesse unique
attendue à plusieurs reprises dans différents endroits

### Références :
<span id="1">1. </span>[article de blog sur zero-cost async stacktraces en v8](https://v8.dev/blog/fast-async)
<br>

<span id="2">2. </span>[Document on zero-cost async stacktraces with mentioned here implementation details](
<span id="2">2. </span>[Document sur zero-cost async stacktraces avec les détails de mise en œuvre mentionnés ici](
https://docs.google.com/document/d/13Sy_kBIJGP0XT34V1CV3nkWya4TwYx9L3Yv45LdGB6Q/edit
)
<br>

0 comments on commit adc053e

Please sign in to comment.