Asynchronisme
Introduction
L’asynchronisme est un concept étroitement lié à JavaScript. Côté navigateur, les appels serveur (requête AJAX), les appels aux API web et certaines fonctions JavaScript (setTimeout, setInterval…) sont résolus de façon asynchrone. Côté serveur (avec par exemple Node.js), toute opération d’entrée/sortie est asynchrone (comme par exemple l’écriture dans un fichier, une requête HTTP, l’accès à une base de données…). L’enchaînement des opérations synchrone et asynchrone peut complexifier la lisibilité du code, c’est pourquoi au fil du temps, différentes évolutions ont permis de simplifier la création et l’utilisation des opérations asynchrones. Au travers des trois sections qui composent ce chapitre, différentes techniques seront étudiées, allant de la plus ancienne (et donc la moins efficace) à la plus récente (les promesses combinées à async/await).
Callback hell
L’utilisation massive de callbacks est considérée comme une mauvaise pratique, on parle alors de Callback hell (l’enfer des fonctions de rappel en français). Elle est également connue sous le nom de "Pyramid of doom" en raison de l’indentation qui croît à chaque appel d’une fonction asynchrone.
Une callback, aussi appelée fonction de rappel, est une fonction appelée à un moment particulier du programme. Les moteurs JavaScript sont monothread, ce qui signifie qu’ils ne peuvent interpréter qu’une seule instruction à la fois. Lorsque le moteur exécutant le code arrive sur un appel asynchrone, il délègue cet appel à son hôte (le navigateur, Node.js…). Ce dernier est souvent multithread, ce qui permet donc de traiter plusieurs instructions à la fois. Lorsqu’il lui délègue cet appel, le moteur fournit à l’hôte une fonction qu’on appelle callback. Après avoir résolu l’opération demandée, l’hôte récupère le résultat et le passe en paramètre de la callback qui lui a été fournie, puis celle-ci est placée dans la pile d’instructions du moteur JavaScript. Le moteur traite les instructions jusqu’à arriver sur la callback et l’exécute....
Promesse
1. Historique
L’utilisation à la chaîne de callback montre depuis toujours des limites, c’est pourquoi une autre solution a vu le jour : les promesses. Le terme promesse a été inventé en 1976 pour qualifier une structure servant à synchroniser l’exécution d’un programme. Il est repris en 1988 par Barbara Liskov et Liuba Shrira qui inventent un système permettant de chaîner des promesses.
En 2010, plusieurs bibliothèques offrent un support des promesses, comme :
-
JQuery (depuis la version 1.5)
-
Q.js
-
When.js
Ces dernières se basent sur une spécification apparue en 2009 qui permet d’uniformiser l’implémentation des promesses : CommonJS Promises/A. Cette spécification a évolué en 2012 pour devenir Promise/A+. Les promesses sont finalement standardisées avec ECMAScript 2015 qui intègre la spécification Promise/A+. Elles deviennent alors nativement utilisables dans JavaScript.
Du côté de Node.js les API ont été implémentées sans utiliser les promesses. C’est pourquoi la fondation Node.js a décidé de conserver en l’état les API pour ne pas provoquer d’incompatibilité dans les applications déjà existantes. Cependant, Node.js fournit une fonction utilitaire promisify qui permet de convertir une callback en promesse. Toutefois, cette fonction a été créée pour être utilisée avec l’API de Node.js, son fonctionnement n’est donc pas garanti lors de la conversion d’une callback provenant d’une bibliothèque tierce. Pour l’anecdote, Ryan Dahl (le créateur de Node.js) a expliqué lors de la conférence JSConf Europe 2018 qu’il avait eu pour intention de baser Node.js sur les promesses. Toutefois, il a finalement choisi l’utilisation des callback, trouvant ces dernières plus faciles à utiliser. Depuis quelques années, Node.js fournit aussi des modules complémentaires aux API de base, et qui implémentent partiellement leur équivalent en utilisant des promesses (exemple : le module fs/promise).
2. Promesses ECMAScript 2015
Définie par la spécification ECMAScript 2015, une promesse est un objet retourné...
Async/await
Apparus pour la première fois dans la version 5 de C#, les mot-clés async/await permettent d’écrire du code s’exécutant de manière asynchrone sous une forme syntaxique synchrone. Ce concept est implémenté en TypeScript depuis 2015 et a été standardisé dans la norme ECMAScript 2017.
Les mots-clés async/await ne remplacent pas les promesses, ils les complètent. Pour déclarer des opérations asynchrones, les promesses sont toujours de rigueur. Cependant, pour appeler et/ou enchaîner les promesses, le couple async/await peut être utilisé afin de simplifier l’écriture du code.
Le mot-clé await permet d’attendre la résolution d’une opération asynchrone et d’en récupérer le résultat. Dès que le mot-clé await est utilisé au sein d’une fonction, celle-ci doit avoir le mot-clé async présent dans sa signature. Ce mot-clé définit que la fonction contient une opération asynchrone et que le moteur JavaScript doit attendre sa résolution pour poursuivre l’exécution du code de la fonction.
Syntaxe :
async function fnName() {
await asynchronousOperation();
}
Exemple :
const getEmployeeSalary = (employeeId: number) => {
return new Promise<number>((resolve, reject) => {
const xhr = new XMLHttpRequest();
xhr.open("GET", `fakeUrl/${employeeId}`);
xhr.onload = function() {
if (this.status === 200) {
resolve(xhr.response);
...