Les callbacks, les promesses et l’async and await
2. Quand est ce qu’une fonction est asynchrone?. 2
Traiter le résultat d’une promesse avec les méthodes then() et catch(). 6
5. Solution basée sur les callbacks. 9
6. Solution basée sur les promesses. 9
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.