Par défaut, toute fonction définie en JavaScript est synchrone. Cela veut dire que, lorsqu’elle est appelée:
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.
// console.log() est une fonction synchrone
console.log('a');
console.log('b');
console.log('c');
// => les lettres a, b et c seront systématiquement affichées dans l'ordre
DĂ©finition de fonction synchrone:
function meaningOfLife () {
return 42;
}
Appel de fonction synchrone:
const answer = meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
Appel de fonction synchrone avec gestion d’erreurs:
try {
const answer = meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
} catch (err) {
console.error('meaningOfLife() a rapporté une erreur:', err);
}
Les fonctions synchrones sont appropriées pour effectuer des opérations courtes, rapides, tant qu’il n’est pas problématique de monopoliser le fil d’exécution du programme Node.js.
Quand on développe un programme effectuant des opérations d’entrées / sorties de données – que ce soit sur le système de fichier, sur un réseau, ou sur n’importe quel matériel périphérique – il vaut mieux lancer ces opérations en tâche de fond, tout en continuant l’exécution du reste du programme.
Par exemple: un serveur doit être prêt à répondre à des requêtes à tout instant, même si une autre requête est déjà en cours de traitement.
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.
Le principe de fonction de callback est la manière la plus classique de procéder:
Ainsi, quand on appelle plusieurs fonctions asynchrones d’affilée, leurs fonctions de callback respectives ne seront pas forcément exécutées dans le même ordre !
// setTimeout() est une fonction asynchrone qui exécute la fonction de callback
// après quelques millisecondes d'attente
setTimeout(() => console.log('a'), 50); // afficher a dans 50 millisecondes
setTimeout(() => console.log('b'), 90); // afficher b dans 90 millisecondes
setTimeout(() => console.log('c'), 20); // afficher c dans 20 millisecondes
// => ordre d'affichage: c, a, puis b
// car les opérations asynchrones s'exécutent en parallèle
En Node.js, une fonction de callback prend généralement deux paramètres:
Error
– si l’opération a échoué – ou null
;return
si notre fonction Ă©tait synchrone.DĂ©finition de fonction Asynchrone avec callback:
// Cette fonction appelera la fonction callback pour transmettre une réponse ou une erreur.
function meaningOfLife (callback) {
// supposons que la réponse est disponible dans une collection mongodb
collection.findOne({ meaning: 'life' }, function (err, res) {
if (err) {
// la requête db a échoué => appeler callback en incluant le message d'erreur
callback(new Error('meaningOfLife failed because ' + err.message));
} else {
// la requête db à réussi => appeler callback en incluant la réponse
callback(null, res.answer);
}
});
}
Appel de fonction Asynchrone avec callback:
meaningOfLife(function (err, answer) {
console.log('valeur retournée par meaningOfLife():', answer);
});
Appel de fonction Asynchrone avec callback + gestion d’erreurs:
meaningOfLife(function (err, answer) {
if (err) {
console.error('meaningOfLife() a rapporté une erreur:', err);
} else {
console.log('valeur retournée par meaningOfLife():', answer);
}
});
Promise
En guise d’alternative à l’usage de fonctions de callback, le concept de promesse (en anglais: Promise
; cf javascript.info et Référence MDN) a été intégré au langage JavaScript pour simplifier le séquençage d’appels asynchrones, et améliorer leur lisibilité en évitant le callback hell.
Ainsi, il est désormais possible de définir une fonction asynchrone en lui faisant retourner une Promise
. Pour récupérer la résultat final de l’exécution de cette fonction, l’appelant doit appeler les fonction .then()
et .catch()
de cette promesse.
Une Promise
peut ĂŞtre:
resolve
), si l’opération asynchrone s’est exécutée avec succès;reject
), si une erreur est survenue pendant l’exécution de cette opération.Les fonctions .then()
et .catch()
permettent de définir le comportement à adopter si la Promise
est résolue ou rejetée, respectivement.
Propriété intéressante: les appels à .then()
et .catch()
retournent la Promise
éventuellement retournée par la fonction passée en paramètre, ce qui rend possible le chaînage de plusieurs opérations asynchrones.
incrémenter(0) // (cette fonction retourne une Promise)
.then((résultat) => {
// résultat === 1
return incrémenter(résultat); // (on retourne à nouveau une Promise)
})
.then((résultat) => {
// résultat === 2
return incrémenter(résultat); // (on retourne à nouveau une Promise)
})
.then((résultat) => {
// résultat === 3
console.log('résultat final:', résultat);
});
DĂ©finition de fonction Asynchrone retournant une Promise
:
// Cette fonction retourne une promesse qui sera tenue ou rejetée.
function meaningOfLife () {
return new Promise(function (resolve, reject) {
// supposons que la réponse est disponible dans une collection mongodb
collection.findOne({ meaning: 'life' })
.then(function (res) {
// la requête db à réussi => appeler resolve en incluant la réponse
resolve(res.answer);
})
.catch(function (err) {
// la requête db a échoué => appeler reject en incluant l'erreur
reject(new Error('meaningOfLife failed because ' + err.message));
})
});
});
}
Appel de fonction Asynchrone retournant une Promise
:
meaningOfLife()
.then(function (answer) {
console.log('valeur retournée par meaningOfLife():', answer);
});
Appel de fonction Asynchrone retournant une Promise
, avec gestion d’erreurs:
meaningOfLife()
.then(function (answer) {
console.log('valeur retournée par meaningOfLife():', answer);
})
.catch(function (err) {
console.error('meaningOfLife() a rapporté une erreur:', err);
});
async
et await
Les mots clés async
et await
ont été introduits plus récemment dans le langage JavaScript pour simplifier et rendre plus lisibles la définition et l’appel de fonctions asynchrones à base de Promesses.
Ils permettent d’écrire du code asynchrone qui ressemble beaucoup à du code synchrone.
Ainsi, quand on appelle trois fonctions asynchrones d’affilée avec await
, on retrouve la garantie qu’elles s’exécutent de manière séquentielle. L’une après l’autre. Sans pour autant bloquer l’exécution des autres opérations asynchrones qui pourraient se dérouller en arrière plan. (ex: répondre à des requêtes)
await afficherPrévisionsMétéoFrance();
await afficherActualitésLeMonde();
await afficherMessagesNonLusDepuisGmail();
// await attend la fin de l'opération avant d'exécuter la suivante.
// => l'ordre d'affichage du résultat de ces opérations est garanti.
async
retourne une Promise
de la valeur retournée avec return
.await
permet d’attendre qu’une Promise
(ex: retournée par l’appel à une fonction async
) soit résolue, puis de récupérer sa valeur de retour.Promise
rejetée – il faut que await
soit employé dans un bloc try {} catch (err) {}
.await
ne peut être utilisé que depuis une fonction async
.DĂ©finition de fonction async
:
async function meaningOfLife () {
try {
// supposons que la réponse est disponible dans une collection mongodb
const res = await collection.findOne({ meaning: 'life' });
return res.answer;
} catch (err) {
throw new Error('meaningOfLife failed because ' + err.message);
}
}
Appel de fonction async
avec await
:
const answer = await meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
Appel de fonction async
avec await
et gestion d’erreurs:
try {
const answer = await meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
} catch (err) {
console.error('meaningOfLife() a rapporté une erreur:', err);
}
Définition et appel d’une fonction anonyme, pour permettre l’usage de await
:
(async function () {
const answer = await meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
})();
Il est aussi possible d’utiliser une fonction fléchée à cette fin:
(async () => {
const answer = await meaningOfLife();
console.log('valeur retournée par meaningOfLife():', answer);
})();