Il existe une syntaxe spĂ©ciale pour travailler avec les promesses dâune maniĂšre plus confortable, appelĂ©e âasync/awaitâ. Elle est Ă©tonnamment facile Ă comprendre et Ă utiliser.
Fonctions asynchrones
Commençons par le mot-clĂ© async. Il peut ĂȘtre placĂ© avant une fonction, comme ceci:
async function f() {
return 1;
}
Le mot âasyncâ devant une fonction signifie une chose simple : une fonction renvoie toujours une promesse. Les autres valeurs sont enveloppĂ©es dans une promesse rĂ©solue automatiquement.
Par exemple, cette fonction renvoie une promesse résolue avec le résultat 1 ; testons-la:
async function f() {
return 1;
}
f().then(alert); // 1
âŠNous pourrions explicitement renvoyer une promesse, ce qui reviendrait au mĂȘme:
async function f() {
return Promise.resolve(1);
}
f().then(alert); // 1
Ainsi, async sâassure que la fonction renvoie une promesse, et enveloppe les non-promesses dans celle-ci. Assez simple, non ? Mais pas seulement. Il y a un autre mot-clĂ©, await, qui ne fonctionne quâĂ lâintĂ©rieur des fonctions async, et câest plutĂŽt cool.
Await
La syntaxe:
// ne fonctionne que dans les fonctions asynchrones
let value = await promise;
Le mot-clé await fait en sorte que JavaScript attende que cette promesse se réalise et renvoie son résultat.
Voici un exemple avec une promesse qui se résout en 1 seconde:
async function f() {
let promise = new Promise((resolve, reject) => {
setTimeout(() => resolve("done!"), 1000)
});
let result = await promise; // attendre que la promesse soit résolue (*)
alert(result); // "done!"
}
f();
LâexĂ©cution de la fonction fait une âpauseâ Ă la ligne (*) et reprend lorsque la promesse sâinstalle, result devenant son rĂ©sultat. Ainsi le code ci-dessus affiche âdone!â en une seconde.
Soulignons-le : await suspend littĂ©ralement lâexĂ©cution de la fonction jusquâĂ ce que la promesse soit rĂ©glĂ©e, puis la reprend avec le rĂ©sultat de la promesse. Cela ne coĂ»te pas de ressources CPU, car le moteur JavaScript peut faire dâautres travaux pendant ce temps : exĂ©cuter dâautres scripts, gĂ©rer des Ă©vĂ©nements, etc.
Câest juste une syntaxe plus Ă©lĂ©gante pour obtenir le rĂ©sultat de la promesse que promise.then. Et câest plus facile Ă lire et Ă Ă©crire.
await dans les fonctions rĂ©guliĂšresSi nous essayons dâutiliser await dans une fonction non-async, il y aurait une erreur de syntaxe:
function f() {
let promise = Promise.resolve(1);
let result = await promise; // Syntax error
}
Nous pouvons obtenir cette erreur si nous oublions de mettre async avant une fonction. Comme indiquĂ© prĂ©cĂ©demment, await ne fonctionne quâĂ lâintĂ©rieur dâune fonction async.
Prenons lâexemple showAvatar() du chapitre ChaĂźnage des promesses et réécrivons-le en utilisant async/await:
- Nous devons remplacer les appels
.thenparawait. - Aussi, nous devrions faire la fonction
asyncpour quâils fonctionnent.
async function showAvatar() {
// lire notre JSON
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
// lire l'utilisateur de github
let githubResponse = await fetch(`https://api.github.com/users/${user.name}`);
let githubUser = await githubResponse.json();
// montrer l'avatar
let img = document.createElement('img');
img.src = githubUser.avatar_url;
img.className = "promise-avatar-example";
document.body.append(img);
// attendre 3 secondes
await new Promise((resolve, reject) => setTimeout(resolve, 3000));
img.remove();
return githubUser;
}
showAvatar();
PlutĂŽt propre et facile Ă lire, non ? Bien mieux quâavant.
await au niveau supĂ©rieur dans les modulesDans les navigateurs modernes, await au niveau supĂ©rieur fonctionne trĂšs bien, lorsque nous sommes dans un module. Nous couvrirons les modules dans lâarticle Modules, introduction.
Par exemple:
// nous supposons que ce code s'exécute au niveau supérieur, dans un module
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
console.log(user);
Si nous nâutilisons pas de modules, ou des navigateurs plus anciens doivent ĂȘtre supportĂ©s, il y a une recette universelle: enveloppement dans une fonction asynchrone anonyme.
Comme ceci :
(async () => {
let response = await fetch('/article/promise-chaining/user.json');
let user = await response.json();
...
})();
await accepte âthenablesâComme promise.then, await nous permet dâutiliser des objets âthenablesâ (ceux qui ont une mĂ©thode then appelable). LâidĂ©e est quâun objet tiers peut ne pas ĂȘtre une promesse, mais ĂȘtre compatible avec les promesses : sâil supporte .then, câest suffisant pour lâutiliser avec awaitâŠ
Voici une classe Thenable de démonstration ; le await ci-dessous accepte ses instances:
class Thenable {
constructor(num) {
this.num = num;
}
then(resolve, reject) {
alert(resolve);
// résous this.num*2 aprÚs 1000ms
setTimeout(() => resolve(this.num * 2), 1000); // (*)
}
}
async function f() {
// attend pendant 1 seconde, puis le résultat devient 2
let result = await new Thenable(1);
alert(result);
}
f();
Si await reçoit un objet non-promis avec .then, il appelle cette mĂ©thode en fournissant les fonctions intĂ©grĂ©es resolve et reject comme arguments (comme pour un exĂ©cuteur Promise normal). Ensuite, await attend que lâune dâentre elles soit appelĂ©e (dans lâexemple ci-dessus, cela se produit Ă la ligne (*)) et procĂšde ensuite avec le rĂ©sultat.
Pour déclarer une méthode de classe asynchrone, il suffit de la faire précéder de async:
class Waiter {
async wait() {
return await Promise.resolve(1);
}
}
new Waiter()
.wait()
.then(alert); // 1 (c'est la mĂȘme chose que (result => alert(result)))
La signification est la mĂȘme : elle assure que la valeur retournĂ©e est une promesse et active await.
Gestion des erreurs
Si une promesse se rĂ©sout normalement, alors await promise renvoie le rĂ©sultat. Mais dans le cas dâun rejet, il jette lâerreur, comme sâil y avait une instruction throw Ă cette ligne.
Ce code:
async function f() {
await Promise.reject(new Error("Whoops!"));
}
âŠest le mĂȘme que celui-ci:
async function f() {
throw new Error("Whoops!");
}
Dans des situations réelles, la promesse peut prendre un certain temps avant de rejeter. Dans ce cas, il y aura un délai avant que await ne lance une erreur.
Nous pouvons rattraper cette erreur en utilisant try..catch, de la mĂȘme maniĂšre quâun throw normal:
async function f() {
try {
let response = await fetch('http://no-such-url');
} catch(err) {
alert(err); // TypeError: failed to fetch
}
}
f();
En cas dâerreur, le contrĂŽle saute au bloc catch. Nous pouvons Ă©galement envelopper plusieurs lignes:
async function f() {
try {
let response = await fetch('/no-user-here');
let user = await response.json();
} catch(err) {
// attrape les erreurs Ă la fois dans fetch et response.json
alert(err);
}
}
f();
Si nous nâavons pas try..catch, alors la promesse gĂ©nĂ©rĂ©e par lâappel de la fonction asynchrone f() sera rejetĂ©e. Nous pouvons ajouter .catch pour le gĂ©rer:
async function f() {
let response = await fetch('http://no-such-url');
}
// f() devient une promesse rejetée
f().catch(alert); // TypeError: failed to fetch // (*)
Si nous oublions dâajouter .catch Ă cet endroit, nous obtenons une erreur de promesse non gĂ©rĂ©e (visible dans la console). Nous pouvons attraper de telles erreurs en utilisant un gestionnaire dâĂ©vĂ©nement global unhandledrejection comme dĂ©crit dans le chapitre Gestion des erreurs avec des promesses.
async/await et promise.then/catchLorsque nous utilisons async/await, nous avons rarement besoin de .then, car await gĂšre lâattente pour nous. Et nous pouvons utiliser un try..catch normal au lieu de .catch. Câest gĂ©nĂ©ralement (mais pas toujours) plus pratique.
Mais au niveau supĂ©rieur du code, lorsque nous sommes en dehors de toute fonction async, nous sommes syntaxiquement incapables dâutiliser await, donc câest une pratique normale dâajouter .then/catch pour gĂ©rer le rĂ©sultat final ou lâerreur de chute, comme dans la ligne (*) de lâexemple ci-dessus.
async/await fonctionne bien avec Promise.allLorsque nous devons attendre plusieurs promesses, nous pouvons les envelopper dans Promise.all et ensuite await:
// attendre le tableau de résultats
let results = await Promise.all([
fetch(url1),
fetch(url2),
...
]);
Dans le cas dâune erreur, elle se propage comme dâhabitude, de la promesse Ă©chouĂ©e Ă Promise.all, et devient alors une exception que nous pouvons attraper en utilisant try..catch autour de lâappel.
Résumé
Le mot-clé async devant une fonction a deux effets:
- Fait en sorte quâelle retourne toujours une promesse.
- Permet lâutilisation de
awaitdans celle-ci.
Le mot-clĂ© await devant une promesse fait en sorte que JavaScript attende jusquâĂ ce que cette promesse se rĂšgle, puis:
- Si câest une erreur, lâexception est gĂ©nĂ©rĂ©e â comme si
throw errorétait appelé à cet endroit précis. - Sinon, il renvoie le résultat.
Ensemble, ils fournissent un cadre idéal pour écrire du code asynchrone facile à lire et à écrire.
Avec async/await, nous avons rarement besoin dâĂ©crire promise.then/catch, mais nous ne devons pas oublier quâils sont basĂ©s sur des promesses, parce que parfois (par exemple dans le scope le plus externe) nous devons utiliser ces mĂ©thodes. De plus, Promise.all est trĂšs utile lorsque lâon attend plusieurs tĂąches simultanĂ©ment.
Commentaires
<code>, pour plusieurs lignes â enveloppez-les avec la balise<pre>, pour plus de 10 lignes - utilisez une sandbox (plnkr, jsbin, codepenâŠ)