JavaScript asynchrone: rappels et promesses expliqués

Gérer du code asynchrone, c'est-à-dire du code qui ne s'exécute pas immédiatement comme les requêtes Web ou les minuteries, peut être délicat. JavaScript nous donne deux moyens prêts à l'emploi pour gérer le comportement asynchrone: les rappels et les promesses.

Les rappels étaient le seul moyen pris en charge nativement pour traiter le code asynchrone jusqu'en 2016, lorsque l' Promiseobjet a été introduit dans le langage. Cependant, les développeurs JavaScript avaient mis en œuvre des fonctionnalités similaires sur leurs propres années avant l'arrivée des promesses. Jetons un coup d'œil à certaines des différences entre les rappels et les promesses, et voyons comment nous gérons la coordination de plusieurs promesses.

Les fonctions asynchrones qui utilisent des rappels prennent une fonction comme paramètre, qui sera appelée une fois le travail terminé. Si vous avez utilisé quelque chose comme setTimeoutdans le navigateur, vous avez utilisé des rappels.

// Vous pouvez définir votre callback séparément ...

laissez myCallback = () => {

  console.log ('Appelé!');

};

setTimeout (myCallback, 3000);

//… mais il est également courant de voir des rappels définis en ligne

setTimeout (() => {

  console.log ('Appelé!');

}, 3000);

Habituellement, la fonction qui prend un rappel le prend comme dernier argument. Ce n'est pas le cas ci-dessus, alors supposons qu'il y ait une nouvelle fonction appelée waitqui est juste comme setTimeoutmais prend les deux premiers arguments dans l'ordre opposé:

// Nous utiliserions notre nouvelle fonction comme ceci:

waitCallback (3000, () => {

  console.log ('Appelé!');

});

Rappels imbriqués et pyramide de malheur

Les rappels fonctionnent bien pour gérer le code asynchrone, mais ils deviennent délicats lorsque vous commencez à avoir à coordonner plusieurs fonctions asynchrones. Par exemple, si nous voulions attendre deux secondes et enregistrer quelque chose, puis attendre trois secondes et enregistrer autre chose, puis attendre quatre secondes et enregistrer autre chose, notre syntaxe devient profondément imbriquée.

// Nous utiliserions notre nouvelle fonction comme ceci:

waitCallback (2000, () => {

  console.log ('Premier rappel!');

  waitCallback (3000, () => {

    console.log ('Deuxième rappel!');

    waitCallback (4000, () => {

      console.log ('Troisième rappel!');

    });

  });

});

Cela peut sembler un exemple trivial (et c'est le cas), mais il n'est pas rare de faire plusieurs requêtes Web d'affilée en fonction des résultats de retour d'une requête précédente. Si votre bibliothèque AJAX utilise des rappels, vous verrez la structure ci-dessus s'exécuter. Dans les exemples plus profondément imbriqués, vous verrez ce que l'on appelle la pyramide de malheur, qui tire son nom de la forme pyramidale faite dans l'espace blanc en retrait au début des lignes.

Comme vous pouvez le voir, notre code est structurellement mutilé et plus difficile à lire lorsqu'il s'agit de fonctions asynchrones qui doivent se produire de manière séquentielle. Mais cela devient encore plus délicat. Imaginez si nous voulions lancer trois ou quatre requêtes Web et effectuer une tâche uniquement après leur retour. Je vous encourage à essayer de le faire si vous n'avez jamais relevé le défi auparavant.

Asynchrone plus facile avec des promesses

Les promesses fournissent une API plus flexible pour traiter les tâches asynchrones. Il nécessite que la fonction soit écrite de telle sorte qu'elle renvoie un Promiseobjet, qui possède certaines fonctionnalités standard pour gérer le comportement ultérieur et coordonner plusieurs promesses. Si notre waitCallbackfonction était Promisebasée sur-, elle ne prendrait qu'un seul argument, qui correspond aux millisecondes à attendre. Toute fonctionnalité ultérieure serait enchaînée à la promesse. Notre premier exemple ressemblerait à ceci:

laissez myHandler = () => {

  console.log ('Appelé!');

};

waitPromise (3000) .then (myHandler);

Dans l'exemple ci-dessus, waitPromise(3000)renvoie un Promiseobjet qui a des méthodes à utiliser, telles que then. Si nous voulions exécuter plusieurs fonctions asynchrones les unes après les autres, nous pourrions éviter la pyramide de malheur en utilisant des promesses. Ce code, réécrit pour soutenir notre nouvelle promesse, ressemblerait à ceci:

// Peu importe le nombre de tâches asynchrones séquentielles que nous avons, nous ne faisons jamais la pyramide.

waitPromise (2000)

  .then (() => {

    console.log ('Premier rappel!');

    return waitPromise (3000);

  })

  .then (() => {

    console.log ('Deuxième rappel!');

    return waitPromise (4000);

  })

  .then (() => {

    console.log ('Deuxième rappel!');

    return waitPromise (4000);

  });

Mieux encore, si nous devons coordonner des tâches asynchrones qui prennent en charge les promesses, nous pourrions utiliser all, qui est une méthode statique sur l' Promiseobjet qui prend plusieurs promesses et les combine en une seule. Cela ressemblerait à:

Promise.all ([

  waitPromise (2000),

  waitPromise (3000),

  waitPromise (4000)

]). then (() => console.log ('Tout est fait!'));

La semaine prochaine, nous approfondirons le fonctionnement des promesses et comment les utiliser de manière idiomatique. Si vous venez d'apprendre JavaScript ou si vous souhaitez tester vos connaissances, essayez waitCallbackou essayez d'accomplir l'équivalent de Promise.allavec des rappels.

Comme toujours, contactez-moi sur Twitter avec des commentaires ou des questions.