Les callbacks, les promesses et l’async and await

1.      Introduction. 1

2.      Quand est ce qu’une fonction est asynchrone?. 2

3.      Promesse. 4

Définition de la promesse. 5

Traiter  le résultat d’une promesse avec les méthodes then() et catch(). 6

Le chainage des promesses. 8

4.      Exemple. 9

5.      Solution basée sur les callbacks. 9

6.      Solution basée sur les promesses. 9

7.      async and await. 10

 

1.    Introduction

On a vu dans les cours précédents que vu la nature de développement web, le JS  était conçu  comme un  langage basé sur un modèle de programmation asynchrone( opérations de réseau et d’I/O). Càd qu’on peut écrire des opérations ou utiliser des opérations dites asynchrone( exemple les opérations qui font appel à des serveurs distants ou lecture des fichiers ). Si pour cette raison qu’écrire des programmes complètement synchrones en utilisant ces types de fonctions ou d’opérations peut affecter négativement les performances des sites web et l’expérience utilisateur.

La solution est alors de programmer d’une manière asynchrone, c.à.d. on lance l’opération asynchrone et nous continuons le travail sans attendre le résultat de  l’opération(ex : on fait des requêtes AJAX et  la page ne sera  pas bloquée malgré la non disponibilité des résultats de la requête  ajax). Une fois le résultat devient  disponible il faut mettre en place les mécanismes nécessaires pour le prendre en charge. Le mécanisme de base couramment utilisé est l’utilisation des  callbacks et les approches avancées qui facilite leurs mise en œuvre tels que les promesses et l’async and await. Un callback est une fonction à appeler plus tard. Généralement elle est  passée en paramètre à une autre fonction et qui sera appelée à la fin de cette dernière pour effectuer une autre opération.

2.    Quand est ce qu’une fonction est asynchrone?

Les programmeurs JS noment une fonction  comme asynchrone si elle peut rendre une autre fonction,  appelé callback et passée comme argument, exécutable  dans le futur  à  partir de l’eventqueue (la file de messages de l’environnement js ).

Exemple

function callback(r){return r*2 ;}

function (a,b,callback){

S=a+b ;

Callback(s) ;

}

Pour expliquer on donne deux exemples

Supposons maintenait que la fonction get_value est une fonction asynchrone qui fait appel au serveur pour connaitre la valeur de la variable imaginaire  v. on va simuler cette fonction par setTimeout. On suppose et en simplifiant que  cette fonction retourne toujours 4.

l’opération setTimeout(function, durée en ms) est une opération asynchrone. Cette fonction appelle une autre fonction passé comme argument après l’écoulement d’une certaine durée exprimée en millisecondes et passée aussi comme un paramètre. La fonction passée en paramètre est une  callback.

 

On va écrire un programme qui lit la variable v du serveur , puis affiche son multiple dans ce cas le résultat sera 8.

La  solution suivante est une solution d’un programmeur venant de l’école de synchrone

 

<script>

console.log("bienvenue")

function get_value(s)

{

h=s;

setTimeout(function () {

    return h*4 ;

}, 1000)

return h;

};

result=get_value(10);

 console.log("resultat="+result);

</script>

<body>

bienvenue

</body>

Le programme ici affiche bienvenue puis indique que le contenu de la variable result est 10 et pas 40

contrairement a ce que attend un programmeur qui vient de l’école de synchrone.

Pourquoi :

 

parce que get_value est une  opération asynchrone , une fois lancée, elle va prendre un certain temps pour s’exécuter. Donc durant cette période le programme ne sera pas bloqué et les instructions qui suivent seront exécutées (ici les instructions Result=get_value(10)

 et console.log("resultat="+result)

 

 

Après l’écoulement de 1000 ms la fonction passée comme argument à  set Timeout s’exécute et retourne 40 mais aucun mécanisme n’est la pour exploiter le résultat.

Exemple 2

Solution basée sur la callback ici traiter

<script>

console.log("bienvenue")

function get_value(callback)

{

setTimeout(function () {

    return callback(4) ;

}, 2000)};

function traiter(s){

resultat=s*2;

console.log("resultat="+resultat)

}

get_value(traiter);

</script>

 

De l’exemple 1 et 2 on voit clairement que la programmation asynchrone demande plus d’attention.

 

3.    Promesse

La majorité des   API web modernes utilisent les promesses pour écrire un code propre facile à lire et à maintenir. Donc il est  important de comprendre leurs principes de fonctionnement et de savoir comment  les utiliser pour écrire un  code bien structuré et propre.

Noter que le grand défi qui face le programmeur js est la gestion de grande quantité de code  asynchrone en se basant  sur le callbacks.

Cette approche a de nombreux  inconvénients, par exemple  la gestion des erreurs à différents  emplacements(if (error) et try {} catch() {}), et la structure  du code qui est  difficile à lire et à maintenir  quand on est forcer   à imbriquer les  " callbacks". Cette imbrication forme une "pyramid of doom" (appelée aussi le "callback hell") difficilement lisible.

Les avantages des promesses par rapport  à une gestion basée sur les callbacks sont :

1.      la possibilité de chainer les opérations asynchrones

2.       la garantie que le déroulement des opérations va être effectuév dans l’ordre voulu

3.      La simplification de la gestion des erreurs en évitant le « callback hell ».

L’objet "Promise" est une approche pour réaliser de manière simple des   opérations asynchrones. Le principe de fonctionnement de la promesse est : on lance une opération de manière asynchrone. Le résultat  de cette opération  sera disponible dans le futur (on a une promesse).

Une promesse sert comme conteneur d’un résultat futur de cette opération asynchrone (exemple : requête http ou lecture à partir du disque ..etc).

Quand une opération async est appelée, elle retourne immédiatement une promesse. A cet objet, on peut attacher des callbacks qui peuvent s’exécuter en cas de succès  ou échec de l’opération(erreur).

A la Promise on passe comme   paramètre une fonction qui va s’exécuter immédiatement. Cette fonction  prend deux paramètres : une  callback qui permet d’indiquer que la promesse a été tenue (tout s’est bien passé donc on retourne le résultat) et une deuxième fonction de callback qui permet cette fois de signaler que la promesse est en échec (possibilité de retourner une erreur).

Les 3  états de   la promise sont :

  • en cours (non terminée) ;
  • terminé avec succès (promesse résolue) ;
  • terminé ou plus exactement stoppée après un échec (promesse rejetée).

 

Le programmeur en JavaScript, peut créer ces  propres promesses ou utiliser des promesses disponibles sur  des API.

Mais en pratique , il va  utiliser beaucoup d’ opérations asynchrone pré-codées et fournies par des API. Ce qui veut dire qu’il est rarement appelé à créer de nouveaux opérations asynchrones, mais il va utilisé des fonctions asynchrone qui retournent des promesses.

Créer une promesse  :

 

Malgré quand en pratique on ne  fait pas de la création des promesses mais on les utilisent, il faut avoir un idée sur cette procédure.

L’idée est la suivante : nous allons définir une fonction pour exécuter   opération asynchrone.  Cette fonction va, lors de son exécution, créer et renvoyer une promesse (un objet).

La procedure de creation

 

new Promise(function(resolve, reject) {
   // ici
//la tache asynchrone à réaliser
// appel à resolve si la promesse est tenue (résolue)
Ou 
//appel  à reject si la promesse est rejeté.
});

 

Si la promesse est tenue, la fonction resolve() sera appelée.

Si la promesse est rompue la fonction reject() va être appelée

Exemple

Charger un script dont l’url est src

 

function loadScript(src){
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        script.src = src;
        document.head.append(script);
        script.onload = function() { resolve('Fichier ' + src + ' bien chargé');};
        script.onerror = function(){ reject(new Error('Echec de chargement de ' + src));} ;
    });
}

 

Pour exploiter le résultat on utilise la méthode then de l’objet  promise.

Cette méthode prend deux callbacks comme arguments.

La  première prend comme argument le résultat de la promesse et  sera appelée si la promesse est résolue.

La deuxième  sera appelée si la promesse est rompue et elle prend comme argument l’erreur survenue .

Exemple

function loadScript(src){
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        script.src = src;
        document.head.append(script);
        script.onload =
function() { resolve('Fichier ' + src + ' bien chargé');};
        script.onerror = function(){
reject(new Error('Echec de chargement de ' + src));} ;
}

const promesse1 = loadScript('script1.js');
const promesse2 = loadScript('script2.js');

promesse1.then(
    function(result){alert(result);},
    function(error){alert(error);}
);

 

Remarque

1.      on peut  passer a  then() une seule fonction callback  en argument qui sera exécutée  si la promesse est tenue.

2.      on peut  passer à la méthode  catch  du promesse comme argument une  fonction de callback  qui sera exécutée  si la promesse est rompue.

Exemple

const promesse1 = loadScript('src.js');
promesse1.catch(alert);

On peut chainer les promesses pour exécuter plusieurs opération asynchrone l’une à la suite de l’autre. Ici les callbacks retournent des promesses.

 

function loadScript(src){
    return new Promise((resolve, reject) => {
        let script = document.createElement('script');
        script.src = src;
        document.head.append(script);
        script.onload = () => resolve('Fichier ' + src + ' bien chargé');
        script.onerror = () => reject(new Error('Echec de chargement de ' + src));
    });
}

loadScript('boucle.js')
.then(result => loadScript('script2.js', result))
.then(result2 => loadScript('script3.js', result2))
.catch(alert);



4.    Exemple

Dans cet exemple on montre la solution du problème de lecture d’un fichier en utilisant l’approche callbacks seulement et les promesses.

5.    Solution basée sur les callbacks

Une fonction asynchrone traditionnelle utilise comme argument une fonction dite "callback" qui sera appelée une fois l'opération asynchrone terminée :

1.      var fs = require('fs');

2.       

3.      fs.readFile('config.json',

4.          function (error, text) {

5.              if (error) {

6.                  console.error('Error while reading config file');

7.              } else {

8.                  try {

9.                      const obj = JSON.parse(text);

10.                  console.log(JSON.stringify(obj, null, 4));

11.              } catch (e) {

12.                  console.error('Invalid JSON in file');

13.              }

14.          }

15.      }

16.  );

 

6.    Solution basée sur les promesses

La version ci-dessous est une utilisation d'une fonction "promessifiée" . cette fonction est facile à lire et  à maintenir

readFilePromisified('config.json')

1.      .then(function (text) {

2.          const obj = JSON.parse(text);

3.          console.log(JSON.stringify(obj, null, 4));

4.      })

5.      .catch(function (reason) {

6.          // File read error or JSON SyntaxError

7.          console.error('An error occurred', reason);

8.      });

Pour transformer notre fonction "à callback" en promesse, il suffit d'écrire le code suivant :

1.      var fs = require('fs');

2.       

3.      function readFilePromisified(filename) {

4.          return new Promise(

5.              function (resolve, reject) {

6.                  fs.readFile(filename, { encoding: 'utf8' },

7.                      (error, data) => {

8.                          if (error) {

9.                              reject(error);

10.                      } else {

11.                          resolve(data);

12.                      }

13.                  });

14.          }

15.      );

16.  }

 

 

7.    async and await

ce travail sera le votre.

 

 


Modifié le: vendredi 15 avril 2022, 15:15