Skip to content

Microtasks #109

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 2 commits into from
Oct 8, 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
48 changes: 24 additions & 24 deletions 1-js/11-async/06-promisify/article.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Promisification

Promisification -- is a long word for a simple transform. It's conversion of a function that accepts a callback into a function returning a promise.
La promisification represente une simple transformation. Il s'agit de la conversion d'une fonction qui accepte une fonction de rappel ("callback") en une fonction renvoyant une promesse.

Such transforms are often needed in real-life, as many functions and libraries are callback-based. But promises are more convenient. So it makes sense to promisify those.
De telles transformations sont souvent nécessaires dans la vie réelle, car de nombreuses fonctions et bibliothèques sont basées sur des rappels. Mais les promesses sont plus pratiques. Il est donc logique de les transformer.

For instance, we have `loadScript(src, callback)` from the chapter <info:callbacks>.
Par exemple, nous avons `loadScript(src, callback)` du chapitre <info:callbacks>.

```js run
function loadScript(src, callback) {
Expand All @@ -21,7 +21,7 @@ function loadScript(src, callback) {
// loadScript('path/script.js', (err, script) => {...})
```

Let's promisify it. The new `loadScriptPromise(src)` function will do the same, but accept only `src` (no `callback`) and return a promise.
Transformons-le. La nouvelle fonction `loadScriptPromise(src)` fera de même, mais acceptera seulement `src` (pas de `callback`) et renverra une promesse.

```js
let loadScriptPromise = function(src) {
Expand All @@ -37,31 +37,31 @@ let loadScriptPromise = function(src) {
// loadScriptPromise('path/script.js').then(...)
```

Now `loadScriptPromise` fits well in promise-based code.
Maintenant, `loadScriptPromise` s'intègre bien dans du code basé sur des promesses.

As we can see, it delegates all the work to the original `loadScript`, providing its own callback that translates to promise `resolve/reject`.
Comme nous pouvons le constater, elle délègue tout le travail au `loadScript` d'origine, en fournissant son propre rappel qui se traduit par la promesse de "résoudre/rejeter".

In practice we'll probably need to promisify many functions, it makes sense to use a helper.
En pratique, nous aurons probablement besoin de promettre de nombreuses fonctions. Il est donc logique d'utiliser une fonction assistante.

We'll call it `promisify(f)`: it accepts a to-promisify function `f` and returns a wrapper function.
Nous l'appellerons `promisify(f)`: elle accepte une fonction à tronsformer `f` et renvoie une fonction wrapper.

That wrapper does the same as in the code above: returns a promise and passes the call to the original `f`, tracking the result in a custom callback:
Ce wrapper fait la même chose que dans le code ci-dessus: renvoie une promesse et passe l'appel au `f` d'origine, en suivant le résultat dans un rappel personnalisé:

```js
function promisify(f) {
return function (...args) { // return a wrapper-function
return function (...args) { // renvoie une fonction wrapper
return new Promise((resolve, reject) => {
function callback(err, result) { // our custom callback for f
function callback(err, result) { // notre rappel personnalisé pour f
if (err) {
return reject(err);
} else {
resolve(result);
}
}

args.push(callback); // append our custom callback to the end of f arguments
args.push(callback); // ajoute notre rappel personnalisé à la fin des arguments de f

f.call(this, ...args); // call the original function
f.call(this, ...args); // appeler la fonction d'origine
});
};
};
Expand All @@ -71,22 +71,22 @@ let loadScriptPromise = promisify(loadScript);
loadScriptPromise(...).then(...);
```

Here we assume that the original function expects a callback with two arguments `(err, result)`. That's what we encounter most often. Then our custom callback is in exactly the right format, and `promisify` works great for such a case.
Nous supposons ici que la fonction d'origine attend un rappel avec deux arguments `(err, result)`. C'est ce que nous rencontrons le plus souvent. Ensuite, notre rappel personnalisé est exactement au bon format et `promisify` convient parfaitement à un tel cas.

But what if the original `f` expects a callback with more arguments `callback(err, res1, res2, ...)`?
Mais que se passe-t-il si le `f` original attend un rappel avec plus d'arguments `callback(err, res1, res2, ...)`?

Here's a more advanced version of `promisify`: if called as `promisify(f, true)`, the promise result will be an array of callback results `[res1, res2, ...]`:
Voici une version plus avancée de `promisify`: si elle est appelée par `promisify(f, true)`, le résultat de la promesse sera un tableau de résultats de rappel `[res1, res2, ...]`:

```js
// promisify(f, true) to get array of results
// promisify(f, true) pour obtenir un tableau de résultats
function promisify(f, manyArgs = false) {
return function (...args) {
return new Promise((resolve, reject) => {
function *!*callback(err, ...results*/!*) { // our custom callback for f
function *!*callback(err, ...results*/!*) { // notre rappel personnalisé pour f
if (err) {
return reject(err);
} else {
// resolve with all callback results if manyArgs is specified
// résoudre avec tous les résultats de rappel si manyArgs est spécifié
*!*resolve(manyArgs ? results : results[0]);*/!*
}
}
Expand All @@ -103,14 +103,14 @@ f = promisify(f, true);
f(...).then(arrayOfResults => ..., err => ...)
```

For more exotic callback formats, like those without `err` at all: `callback(result)`, we can promisify such functions without using the helper, manually.
Pour des formats de rappel plus exotiques, comme ceux sans `err`: `callback(résultat)`, nous pouvons transformer de telles fonctions manuellement, sans utiliser la fonction assistante.

There are also modules with a bit more flexible promisification functions, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). In Node.js, there's a built-in `util.promisify` function for that.
Il existe également des modules avec des fonctions de promisification un peu plus flexibles, e.g. [es6-promisify](https://github.com/digitaldesignlabs/es6-promisify). Dans Node.js, il existe une fonction intégrée `util.promisify` pour cela.

```smart
Promisification is a great approach, especially when you use `async/await` (see the next chapter), but not a total replacement for callbacks.
La promisification est une excellente approche, surtout lorsque vous utilisez `async/wait` (voir le chapitre suivant), mais ne remplace pas totalement les rappels.

Remember, a promise may have only one result, but a callback may technically be called many times.
N'oubliez pas qu'une promesse peut avoir un seul résultat, mais un rappel peut techniquement être appelé plusieurs fois.

So promisification is only meant for functions that call the callback once. Further calls will be ignored.
La promisification ne concerne donc que les fonctions qui appellent le rappel une fois. D'autres appels seront ignorés.
```
74 changes: 37 additions & 37 deletions 1-js/11-async/07-microtask-queue/article.md
Original file line number Diff line number Diff line change
@@ -1,78 +1,78 @@

# Microtasks
# Les microtaches

Promise handlers `.then`/`.catch`/`.finally` are always asynchronous.
Les gestionnaires de promesses `.then`/`.catch`/`.finally` sont toujours asynchrones.

Even when a Promise is immediately resolved, the code on the lines *below* `.then`/`.catch`/`.finally` will still execute before these handlers .
Même lorsqu'une promesse est immédiatement résolue, le code sur les lignes situées *ci-dessous* `.then`/`.catch`/`.finally` sera toujours exécuté avant ces gestionnaires.

Here's the demo:
Voici la démo:

```js run
let promise = Promise.resolve();

promise.then(() => alert("promise done!"));

alert("code finished"); // this alert shows first
alert("code finished"); // cette alerte s'affiche d'abord
```

If you run it, you see `code finished` first, and then `promise done!`.
Si vous exécutez, vous voyez `code finished` d'abord, puis `promise done!`.

That's strange, because the promise is definitely done from the beginning.
C'est étrange, car la promesse est certainement résolue depuis le début.

Why did the `.then` trigger afterwards? What's going on?
Pourquoi le `.then` se déclenche par la suite? Que se passe-t-il?

## Microtasks queue
## File d'attente pour microtaches

Asynchronous tasks need proper management. For that, the standard specifies an internal queue `PromiseJobs`, more often referred to as "microtask queue" (v8 term).
Les tâches asynchrones nécessitent une gestion appropriée. Pour cela, la norme spécifie une file d'attente interne `PromiseJobs`, plus souvent appelée "microtask queue" en anglais (terme v8).

As said in the [specification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):
Comme indiqué dans la [spécification](https://tc39.github.io/ecma262/#sec-jobs-and-job-queues):

- The queue is first-in-first-out: tasks enqueued first are run first.
- Execution of a task is initiated only when nothing else is running.
- La file d'attente est premier entré, premier sorti: les tâches mises en file d'attente en premier sont exécutées en premier.
- L'exécution d'une tâche est lancée uniquement lorsque rien d'autre n'est en cours d'exécution.

Or, to say that simply, when a promise is ready, its `.then/catch/finally` handlers are put into the queue. They are not executed yet. JavaScript engine takes a task from the queue and executes it, when it becomes free from the current code.
Ou, simplement, lorsqu'une promesse est prête, ses gestionnaires `.then/catch/finally` sont mis en file d'attente. Ils ne sont pas encore exécutés. Le moteur JavaScript extrait une tâche de la file d'attente et l'exécute lorsqu'elle est libérée du code actuel.

That's why "code finished" in the example above shows first.
C'est pourquoi "code finished" dans l'exemple ci-dessus s'affiche en premier.

![](promiseQueue.svg)

Promise handlers always go through that internal queue.
Les gestionnaires de promesses passent toujours par cette file d'attente interne.

If there's a chain with multiple `.then/catch/finally`, then every one of them is executed asynchronously. That is, it first gets queued, and executed when the current code is complete and previously queued handlers are finished.
S'il existe une chaîne avec plusieurs `.then/catch/finally`, chacun d'entre eux est exécuté de manière asynchrone. C'est-à-dire qu'il est d'abord mis en file d'attente et exécuté lorsque le code actuel est terminé et que les gestionnaires précédemment placés en file d'attente sont terminés.

**What if the order matters for us? How can we make `code finished` work after `promise done`?**
**Et si l'order importait pour nous? Comment pouvons-nous faire en sorte que `code finished` s'exécute après `promise done`?**

Easy, just put it into the queue with `.then`:
Facile, il suffit de le mettre dans la file d'attente avec `.then`:

```js run
Promise.resolve()
.then(() => alert("promise done!"))
.then(() => alert("code finished"));
```

Now the order is as intended.
Maintenant, l'ordre est comme prévu.

## Unhandled rejection
## Rejet non traité

Remember `unhandledrejection` event from the chapter <info:promise-error-handling>?
Souvenez-vous de l'événement `unhandledrejection` du chapitre <info:promise-error-handling>?

Now we can see exactly how JavaScript finds out that there was an unhandled rejection
Maintenant, nous pouvons voir exactement comment JavaScript découvre qu'il y a eu un rejet non géré

**"Unhandled rejection" occurs when a promise error is not handled at the end of the microtask queue.**
**Un rejet non traité se produit lorsqu'une erreur de promesse n'est pas traitée à la fin de la file d'attente des microtaches.**

Normally, if we expect an error, we add `.catch` to the promise chain to handle it:
Normalement, si nous nous attendons à une erreur, nous ajoutons `.catch` dans la chaîne de promesse pour la gérer:

```js run
let promise = Promise.reject(new Error("Promise Failed!"));
*!*
promise.catch(err => alert('caught'));
*/!*

// doesn't run: error handled
// n'exécute pas: erreur gérée
window.addEventListener('unhandledrejection', event => alert(event.reason));
```

...But if we forget to add `.catch`, then, after the microtask queue is empty, the engine triggers the event:
...Mais si nous oublions d’ajouter `.catch`, le moteur déclenche l’événement une fois que la file d’attente de microtaches est vide:

```js run
let promise = Promise.reject(new Error("Promise Failed!"));
Expand All @@ -81,7 +81,7 @@ let promise = Promise.reject(new Error("Promise Failed!"));
window.addEventListener('unhandledrejection', event => alert(event.reason));
```

What if we handle the error later? Like this:
Et si nous gérons l'erreur plus tard? Comme ceci:

```js run
let promise = Promise.reject(new Error("Promise Failed!"));
Expand All @@ -93,20 +93,20 @@ setTimeout(() => promise.catch(err => alert('caught')), 1000);
window.addEventListener('unhandledrejection', event => alert(event.reason));
```

Now, if you run it, we'll see `Promise Failed!` message first, and then `caught`.
Maintenant, si vous l'exécutez, nous verrons d'abord le message `Promise Failed!`, Puis `caught`.

If we didn't know about microtasks queue, we could wonder: "Why did `unhandledrejection` handler run? We did catch the error!".
Si nous ne connaissions pas la file d'attente des microtaches, nous pourrions nous demander: "Pourquoi le gestionnaire `unhandledrejection` a-t-il été exécuté? Nous avons détecté l'erreur!".

But now we understand that `unhandledrejection` is generated when the microtask queue is complete: the engine examines promises and, if any of them is in "rejected" state, then the event triggers.
Mais nous comprenons maintenant que `unhandledrejection` est généré à la fin de la file d'attente des microtaches: le moteur examine les promesses et, si l'une d'entre elles est à l'état "rejected", l'événement se déclenche.

In the example above, `.catch` added by `setTimeout` also triggers, but later, after `unhandledrejection` has already occurred, so that doesn't change anything.
Dans l'exemple ci-dessus, `.catch` ajouté par `setTimeout` se déclenche également, mais plus tard, après que `unhandledrejection` se soit déjà produit, cela ne change rien.

## Summary
## Résumé

Promise handling is always asynchronous, as all promise actions pass through the internal "promise jobs" queue, also called "microtask queue" (v8 term).
Le traitement des promesses est toujours asynchrone, car toutes les actions de promesse passent par la file d'attente interne "promise jobs", également appelée "file d'attente pour microtaches" (terme v8).

So, `.then/catch/finally` handlers are always called after the current code is finished.
Ainsi, les gestionnaires `.then/catch/finally` sont toujours appelés une fois le code actuel terminé.

If we need to guarantee that a piece of code is executed after `.then/catch/finally`, we can add it into a chained `.then` call.
Si nous devons garantir qu'un morceau de code est exécuté après `.then/catch/finally`, nous pouvons l'ajouter à un appel `.then` enchaîné.

In most Javascript engines, including browsers and Node.js, the concept of microtasks is closely tied with "event loop" and "macrotasks". As these have no direct relation to promises, they are covered in another part of the tutorial, in the chapter <info:event-loop>.
Dans la plupart des moteurs Javascript, y compris les navigateurs et Node.js, le concept de microtaches est étroitement lié à la "boucle d'événement" et aux "macrotaches". Comme elles n’ont pas de relation directe avec les promesses, elles sont décrites dans une autre partie du didacticiel, au chapitre <info:event-loop>.