Skip to content

Introduction: callbacks #329

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 1 commit into from
Oct 19, 2021
Merged
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
138 changes: 69 additions & 69 deletions 1-js/11-async/01-callbacks/article.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,67 +2,67 @@

# Introduction: callbacks

```warn header="We use browser methods in examples here"
To demonstrate the use of callbacks, promises and other abstract concepts, we'll be using some browser methods: specifically, loading scripts and performing simple document manipulations.
```warn header="Nous utilisons ici des méthodes du navigateur dans les exemples"
Pour démontrer l'utilisation des callbacks, des promesses et d'autres concepts abstraits, nous utiliserons certaines méthodes du navigateur : plus précisément, nous chargerons des scripts et effectuerons des manipulations simples de documents.

If you're not familiar with these methods, and their usage in the examples is confusing, you may want to read a few chapters from the [next part](/document) of the tutorial.
Si vous n'êtes pas familier avec ces méthodes, et que leur utilisation dans les exemples est confuse, vous pouvez lire quelques chapitres de la [partie suivante](/document) du tutoriel.

Although, we'll try to make things clear anyway. There won't be anything really complex browser-wise.
Mais nous allons quand même essayer de rendre les choses claires. Il n'y aura rien de vraiment complexe au niveau du navigateur.
```

Many functions are provided by JavaScript host environments that allow you to schedule *asynchronous* actions. In other words, actions that we initiate now, but they finish later.
De nombreuses fonctions sont fournies par les environnements hôtes JavaScript qui vous permettent de planifier des actions *asynchrones*. En d'autres termes, des actions que nous lançons maintenant, mais qui se terminent plus tard.

For instance, one such function is the `setTimeout` function.
Par exemple, une de ces fonctions est la fonction `setTimeout`.

There are other real-world examples of asynchronous actions, e.g. loading scripts and modules (we'll cover them in later chapters).
Il existe d'autres exemples concrets d'actions asynchrones, par exemple le chargement de scripts et de modules (nous les aborderons dans les chapitres suivants).

Take a look at the function `loadScript(src)`, that loads a script with the given `src`:
Regardez la fonction `loadScript(src)`, qui charge un script avec le `src` donné:

```js
function loadScript(src) {
// creates a <script> tag and append it to the page
// this causes the script with given src to start loading and run when complete
// crée une balise <script> et l'ajoute à la page
// ceci fait que le script avec la src donnée commence à se charger et s'exécute une fois terminé.
let script = document.createElement('script');
script.src = src;
document.head.append(script);
}
```

It inserts into the document a new, dynamically created, tag `<script src="">` with the given `src`. The browser automatically starts loading it and executes when complete.
Il insère dans le document une nouvelle balise, créée dynamiquement, `<script src="...">` avec le `src` donné. Le navigateur commence automatiquement à la charger et l'exécute lorsqu'elle est terminée.

We can use this function like this:
Nous pouvons utiliser cette fonction comme suit :

```js
// load and execute the script at the given path
// charger et exécuter le script au chemin donné
loadScript('/my/script.js');
```

The script is executed "asynchronously", as it starts loading now, but runs later, when the function has already finished.
Le script est exécuté de manière "asynchrone", car il commence à se charger maintenant, mais s'exécute plus tard, lorsque la fonction est déjà terminée.

If there's any code below `loadScript()`, it doesn't wait until the script loading finishes.
S'il y a du code sous `loadScript(...)`, il n'attend pas que le chargement du script soit terminé.

```js
loadScript('/my/script.js');
// the code below loadScript
// doesn't wait for the script loading to finish
// le code dessous loadScript
// n'attend pas que le chargement du script soit terminé
// ...
```

Let's say we need to use the new script as soon as it loads. It declares new functions, and we want to run them.
Disons que nous devons utiliser le nouveau script dès qu'il est chargé. Il déclare de nouvelles fonctions, et nous voulons les exécuter.

But if we do that immediately after the `loadScript(…)` call, that wouldn't work:
Mais si nous le faisons immédiatement après l'appel `loadScript(...)`, cela ne fonctionnera pas:

```js
loadScript('/my/script.js'); // the script has "function newFunction() {…}"
loadScript('/my/script.js'); // le script a "function newFunction() {…}"

*!*
newFunction(); // no such function!
newFunction(); // aucune fonction de ce type!
*/!*
```

Naturally, the browser probably didn't have time to load the script. As of now, the `loadScript` function doesn't provide a way to track the load completion. The script loads and eventually runs, that's all. But we'd like to know when it happens, to use new functions and variables from that script.
Naturellement, le navigateur n'a probablement pas eu le temps de charger le script. Pour l'instant, la fonction `loadScript` ne permet pas de suivre l'achèvement du chargement. Le script se charge et finit par s'exécuter, c'est tout. Mais nous aimerions savoir quand cela se produit, pour utiliser les nouvelles fonctions et variables de ce script.

Let's add a `callback` function as a second argument to `loadScript` that should execute when the script loads:
Ajoutons une fonction `callback` comme second argument à `loadScript` qui doit s'exécuter lorsque le script se charge :

```js
function loadScript(src, *!*callback*/!*) {
Expand All @@ -77,19 +77,19 @@ function loadScript(src, *!*callback*/!*) {
}
```

Now if we want to call new functions from the script, we should write that in the callback:
Maintenant, si nous voulons appeler de nouvelles fonctions depuis le script, nous devons l'écrire dans le callback:

```js
loadScript('/my/script.js', function() {
// the callback runs after the script is loaded
newFunction(); // so now it works
// le callback est exécuté après le chargement du script
newFunction(); // maintenant cela fonctionne
...
});
```

That's the idea: the second argument is a function (usually anonymous) that runs when the action is completed.
C'est l'idée: le deuxième argument est une fonction (généralement anonyme) qui s'exécute lorsque l'action est terminée.

Here's a runnable example with a real script:
Voici un exemple exécutable avec un vrai script :

```js run
function loadScript(src, callback) {
Expand All @@ -102,20 +102,20 @@ function loadScript(src, callback) {
*!*
loadScript('https://cdnjs.cloudflare.com/ajax/libs/lodash.js/3.2.0/lodash.js', script => {
alert(`Cool, the script ${script.src} is loaded`);
alert( _ ); // function declared in the loaded script
alert( _ ); // fonction déclarée dans le script chargé
});
*/!*
```

That's called a "callback-based" style of asynchronous programming. A function that does something asynchronously should provide a `callback` argument where we put the function to run after it's complete.
C'est ce qu'on appelle un style de programmation asynchrone basé sur les "callbacks". Une fonction qui fait quelque chose de manière asynchrone doit fournir un argument `callback` où nous mettons la fonction à exécuter après qu'elle soit terminée.

Here we did it in `loadScript`, but of course it's a general approach.
Ici nous l'avons fait dans `loadScript`, mais bien sûr c'est une approche générale.

## Callback in callback
## Callback imbriqué

How can we load two scripts sequentially: the first one, and then the second one after it?
Comment charger deux scripts de manière séquentielle: le premier, puis le second après lui ?

The natural solution would be to put the second `loadScript` call inside the callback, like this:
La solution naturelle serait de placer le second appel `loadScript` à l'intérieur du callback, comme ceci:

```js
loadScript('/my/script.js', function(script) {
Expand All @@ -131,9 +131,9 @@ loadScript('/my/script.js', function(script) {
});
```

After the outer `loadScript` is complete, the callback initiates the inner one.
Une fois que le `loadScript` externe est terminé, le callback lance le `loadScript` interne.

What if we want one more script...?
Et si nous voulons un script de plus... ?

```js
loadScript('/my/script.js', function(script) {
Expand All @@ -142,7 +142,7 @@ loadScript('/my/script.js', function(script) {

*!*
loadScript('/my/script3.js', function(script) {
// ...continue after all scripts are loaded
// ...continue après que tous les scripts soient chargés
});
*/!*

Expand All @@ -151,13 +151,13 @@ loadScript('/my/script.js', function(script) {
});
```

So, every new action is inside a callback. That's fine for few actions, but not good for many, so we'll see other variants soon.
Ainsi, chaque nouvelle action se trouve dans une callback. C'est bien pour peu d'actions, mais pas pour beaucoup, donc nous verrons bientôt d'autres variantes.

## Handling errors
## Gestion des erreurs

In the above examples we didn't consider errors. What if the script loading fails? Our callback should be able to react on that.
Dans les exemples ci-dessus, nous n'avons pas tenu compte des erreurs. Que se passe-t-il si le chargement du script échoue ? Notre callback doit être capable de réagir à cette situation.

Here's an improved version of `loadScript` that tracks loading errors:
Voici une version améliorée de `loadScript` qui suit les erreurs de chargement :

```js
function loadScript(src, callback) {
Expand All @@ -173,32 +173,32 @@ function loadScript(src, callback) {
}
```

It calls `callback(null, script)` for successful load and `callback(error)` otherwise.
Il appelle `callback(null, script)` en cas de chargement réussi et `callback(error)` dans le cas contraire.

The usage:
L'utilisation:
```js
loadScript('/my/script.js', function(error, script) {
if (error) {
// handle error
// erreur dans le chargement du script
} else {
// script loaded successfully
// script chargé avec succès
}
});
```

Once again, the recipe that we used for `loadScript` is actually quite common. It's called the "error-first callback" style.
Une fois encore, la recette que nous avons utilisée pour `loadScript` est en fait assez commune. C'est le style "error-first callback".

The convention is:
1. The first argument of the `callback` is reserved for an error if it occurs. Then `callback(err)` is called.
2. The second argument (and the next ones if needed) are for the successful result. Then `callback(null, result1, result2)` is called.
La convention est:
1. Le premier argument de la `callback` est réservé pour une erreur si elle se produit. Ensuite, `callback(err)` est appelé.
2. Le deuxième argument (et les suivants si nécessaire) sont pour le résultat réussi. Ensuite, `callback(null, result1, result2...)` est appelé.

So the single `callback` function is used both for reporting errors and passing back results.
Ainsi, la fonction unique `callback` est utilisée à la fois pour signaler les erreurs et pour renvoyer les résultats.

## Pyramid of Doom
## Pyramide du malheur

From the first look, it's a viable way of asynchronous coding. And indeed it is. For one or maybe two nested calls it looks fine.
À première vue, il s'agit d'un moyen viable de codage asynchrone. Et c'est effectivement le cas. Pour un ou peut-être deux appels imbriqués, cela semble correct.

But for multiple asynchronous actions that follow one after another we'll have code like this:
Mais pour de multiples actions asynchrones qui se succèdent, nous aurons un code comme celui-ci:

```js
loadScript('1.js', function(error, script) {
Expand All @@ -217,7 +217,7 @@ loadScript('1.js', function(error, script) {
handleError(error);
} else {
*!*
// ...continue after all scripts are loaded (*)
// ...continue après que tous les scripts soient chargés (*)
*/!*
}
});
Expand All @@ -228,14 +228,14 @@ loadScript('1.js', function(error, script) {
});
```

In the code above:
1. We load `1.js`, then if there's no error.
2. We load `2.js`, then if there's no error.
3. We load `3.js`, then if there's no error -- do something else `(*)`.
Dans le code ci-dessus:
1. Nous chargeons `1.js`, puis s'il n'y a pas d'erreur.
2. Nous chargeons `2.js`, puis s'il n'y a pas d'erreur.
3. Nous chargeons `3.js`, puis s'il n'y a pas d'erreur -- fait autre chose `(*)`.

As calls become more nested, the code becomes deeper and increasingly more difficult to manage, especially if we have real code instead of `...` that may include more loops, conditional statements and so on.
Au fur et à mesure que les appels deviennent plus imbriqués, le code devient plus profond et de plus en plus difficile à gérer, surtout si nous avons du vrai code au lieu de `...` qui peut inclure plus de boucles, des déclarations conditionnelles et ainsi de suite.

That's sometimes called "callback hell" or "pyramid of doom."
C'est ce qu'on appelle parfois "l'enfer du rappel" ou "la pyramide du malheur".

<!--
loadScript('1.js', function(error, script) {
Expand Down Expand Up @@ -263,11 +263,11 @@ loadScript('1.js', function(error, script) {

![](callback-hell.svg)

The "pyramid" of nested calls grows to the right with every asynchronous action. Soon it spirals out of control.
La "pyramide" d'appels imbriqués croît vers la droite à chaque action asynchrone. Bientôt, elle devient incontrôlable.

So this way of coding isn't very good.
Donc cette façon de coder n'est pas très bonne.

We can try to alleviate the problem by making every action a standalone function, like this:
Nous pouvons essayer d'atténuer le problème en faisant de chaque action une fonction autonome, comme ceci:

```js
loadScript('1.js', step1);
Expand All @@ -294,17 +294,17 @@ function step3(error, script) {
if (error) {
handleError(error);
} else {
// ...continue after all scripts are loaded (*)
// ...continue après que tous les scripts soient chargés (*)
}
}
```

See? It does the same, and there's no deep nesting now because we made every action a separate top-level function.
Vous voyez ? Il fait la même chose, et il n'y a pas d'imbrication profonde maintenant parce que nous avons fait de chaque action une fonction séparée de haut niveau.

It works, but the code looks like a torn apart spreadsheet. It's difficult to read, and you probably noticed that one needs to eye-jump between pieces while reading it. That's inconvenient, especially if the reader is not familiar with the code and doesn't know where to eye-jump.
Cela fonctionne, mais le code ressemble à une feuille de calcul déchirée. Il est difficile à lire, et vous avez probablement remarqué qu'il faut passer d'un morceau à l'autre en le lisant. Ce n'est pas pratique, surtout si le lecteur n'est pas familier avec le code et ne sait pas où sauter du regard.

Also, the functions named `step*` are all of single use, they are created only to avoid the "pyramid of doom." No one is going to reuse them outside of the action chain. So there's a bit of namespace cluttering here.
De plus, les fonctions nommées `step*` sont toutes à usage unique, elles sont créées uniquement pour éviter la "pyramide du malheur". Personne ne va les réutiliser en dehors de la chaîne d'action. Il y a donc un peu d'encombrement de l'espace de noms ici.

We'd like to have something better.
Nous aimerions avoir quelque chose de mieux.

Luckily, there are other ways to avoid such pyramids. One of the best ways is to use "promises," described in the next chapter.
Heureusement, il existe d'autres moyens d'éviter de telles pyramides. L'un des meilleurs moyens est d'utiliser des "promesses", décrites dans le chapitre suivant.