Notion de synchrone, asynchrone et callback

Objectifs

  • Comprendre synchrone et asynchrone

  • Comprendre les fonctions de rappels (callback)

Mise en situation

Dans sa forme la plus élémentaire, JavaScript est un langage synchrone, bloquant et à un seul processus, dans lequel une seule opération peut être en cours à la fois. Mais les navigateurs web définissent des fonctions et des API qui nous permettent d'enregistrer des fonctions qui ne doivent pas être exécutées de manière synchrone, mais qui doivent être invoquées de manière asynchrone lorsqu'un évènement quelconque se produit (le passage du temps, l'interaction de l'utilisateur avec la souris ou l'arrivée de données sur le réseau, par exemple).

Cela signifie que vous pouvez laisser votre code faire plusieurs choses en même temps sans arrêter ou bloquer votre processus principal

Différence : synchrone et asynchrone

En informatique, on dit que deux opérations sont synchrones lorsque la seconde attend que la première ait fini son travail pour démarrer. Ce qu'il faut retenir de cette définition est le concept de dépendance (la notion de « synchronisation » dans la première définition donnée de synchrone au-dessus) : le début de l'opération suivante dépend de la complétude de l'opération précédente.

Au contraire, deux opérations sont qualifiées d'asynchrones en informatique lorsqu'elles sont indépendantes. C'est-à-dire lorsque la deuxième opération n'a pas besoin d'attendre que la première se termine pour démarrer.

Par défaut, toute fonction définie en JavaScript est synchrone. Cela veut dire que lorsqu'elle est appelée :

  • Cette fonction exécute immédiatement l'intégralité de ses instructions puis retourne une valeur dans la foulée ;

  • Et que le reste du programme attend la fin de l'exécution de cette fonction avant de s'exécuter à son tour.

Ainsi, quand on appelle plusieurs fonctions synchrones d'affilée, on a la garantie qu'elles s'exécutent de manière séquentielle. L'une après l'autre.

Exemple

1
<code>
2
// console.log() est une fonction synchrone
3
console.log('a');
4
console.log('b');
5
console.log('c');
6
// => les lettres a, b et c seront systématiquement affichées dans l'ordre
7
<code>

Pour permettre l'exécution de plusieurs opérations en parallèle, sans bloquer l'exécution du reste du programme, le langage JavaScript fournit plusieurs manières de définir et d'appeler des fonctions asynchrones.

Exemple

La méthode setTimeout() qui permet d'exécuter une fonction de rappel (un callback) après un certain délai.

1
/*setTimeout() est asynchrone : le reste du script va pouvoir s'exécuter
2
 *sans avoir à attendre la fin de l'exécution de setTimeout()*/
3
setTimeout(alert, 5000, 'Message affiché après 5 secondes');
4
5
//Cette alerte sera affichée avant celle définie dans setTimeout()
6
alert('Suite du script');

Les fonctions de rappel, du callback au callback hell (l'enfer des callbacks)

Un rappel est une fonction qui est passée à une autre fonction en tant qu'argument à exécuter ultérieurement. Les développeur disent que vous « appelez » une fonction lorsque vous exécutez une fonction, c'est pourquoi les rappels sont nommés rappels.

Exemple

Un exemple de fonction qui accepte un rappel est addEventListener :

1
<code>
2
const button = document.querySelector('button')
3
button.addEventListener('click', function(e) {
4
  // Adds clicked class to button
5
  this.classList.add('clicked')
6
})
7
</code>

Vous ne voyez pas pourquoi il s'agit d'un rappel ?

1
<code>
2
const button = document.querySelector('button')
3
// Function that adds 'clicked' class to the element
4
function clicked (e) {
5
  this.classList.add('clicked')
6
}
7
8
// Adds click function as a callback to the event listener
9
button.addEventListener('click', clicked)
10
</code>

Ici, nous avons dit à JavaScript d'écouter l'événement click sur un bouton. Si un clic est détecté, JavaScript doit déclencher la fonction clicked. Donc dans ce cas, clicked est le rappel tandis que addEventListener est une fonction qui accepte un rappel.

1
<code>
2
// Create a function that accepts another function as an argument
3
const callbackAcceptingFunction = (fn) => {
4
  // Calls the function with any required arguments
5
  return fn(1, 2, 3)
6
}
7
8
// Callback gets arguments from the above call
9
const callback = (arg1, arg2, arg3) => {
10
  return arg1 + arg2 + arg3
11
}
12
13
// Passing a callback into a callback accepting function
14
const result = callbackAcceptingFunction(callback)
15
console.log(result) // 6
16
17
</code>

Les problèmes arrivent quand une fonction est imbriquée dans une fonction, dans une fonction, dans une fonction, ...

Le callback hell est un phénomène où plusieurs rappels sont imbriqués les uns dans les autres. Cela peut arriver lorsque vous effectuez une activité asynchrone qui dépend d'une activité asynchrone précédente. Ces rappels imbriqués rendent le code beaucoup plus difficile à lire.

ExempleExemple de code non testable mais que vous pouvez retrouver dans un projet

1
<code>
2
const makeBurger = nextStep => {
3
  getBeef(function (beef) {
4
    cookBeef(beef, function (cookedBeef) {
5
      getBuns(function (buns) {
6
        putBeefBetweenBuns(buns, beef, function(burger) {
7
          nextStep(burger)
8
        })
9
      })
10
    })
11
  })
12
}
13
14
// Make and serve the burger
15
makeBurger(function (burger) => {
16
  serve(burger)
17
})
18
19
</code>

Bien heureusement, il existe d'autres solutions pour lutter contre l'enfer des rappels dans les nouvelles versions de JavaScript, telles que les promesses et async/wait.