await
Baseline
Large disponibilité
*
Cette fonctionnalité est bien établie et fonctionne sur de nombreux appareils et versions de navigateurs. Elle est disponible sur tous les navigateurs depuis mars 2017.
* Certaines parties de cette fonctionnalité peuvent bénéficier de prise en charge variables.
L'opérateur await est utilisé pour attendre une promesse (Promise) et obtenir sa valeur de complétion (fulfill en anglais). Il ne peut être utilisé qu'à l'intérieur d'une fonction asynchrone ou au niveau supérieur d'un module.
Syntaxe
await expression
Paramètres
expression-
Une promesse (
Promise), un objet de semi-promesse, ou toute autre valeur dont on souhaite attendre.
Valeur de retour
La valeur de complétion de la promesse ou de l'objet semi-promesse, ou, si l'expression n'est pas semi-promesse, la valeur propre de l'expression.
Exceptions
Lève la raison du rejet si la promesse ou l'objet semi-promesse est rompue (reject en anglais).
Description
await est généralement utilisé pour déballer les promesses en passant une promesse (Promise) comme expression. L'utilisation de await suspend l'exécution de la fonction async englobante jusqu'à ce que la promesse soit acquittée (settle en anglais) (c'est-à-dire complétée ou rompue). Lorsque l'exécution reprend, la valeur de l'expression await devient celle de la promesse complétée.
Si la promesse est rompue, l'expression await lève la valeur de rejet. La fonction contenant l'expression await apparaît dans la trace de la pile de l'erreur. Sinon, si la promesse rompue n'est pas attendue ou est immédiatement retournée, la fonction appelante n'apparaît pas dans la trace de la pile.
Une expression est résolue de la même manière que Promise.resolve() : elle est toujours convertie en une Promise native, puis attendue. Si une expression est :
- Une
Promisenative (ce qui signifie queexpressionappartient àPromiseou à une sous-classe, et queexpression.constructor === Promise) : la promesse est directement utilisée et attendue nativement, sans appelerthen(). - Objet semi-promesse (y compris les promesses qui ne sont pas natives, des prothèses d'émulation, des mandataires, sous-classes, etc.) : une nouvelle promesse est construite avec le constructeur natif
Promise()en appelant la méthodethen()de l'objet et en passant un gestionnaire qui appelle la fonction de rappelresolve. - Valeur qui n'est pas semi-promesse : une
Promisedéjà acquittée est construite et utilisée.
Même lorsque la promesse utilisée est déjà acquittée, l'exécution de la fonction asynchrone est toujours suspendue jusqu'au battement d'horloge suivant. Pendant ce temps, l'appelant de la fonction asynchrone reprend l'exécution. Voir l'exemple ci-dessous.
Comme await n'est valide qu'à l'intérieur des fonctions asynchrones et des modules, qui sont eux-mêmes asynchrones et retournent des promesses, l'expression await ne bloque jamais le thread principal et ne fait que différer l'exécution du code qui dépend réellement du résultat, c'est-à-dire de tout ce qui se trouve après l'expression await.
Exemples
>Attendre la complétion d'une promesse
Si une promesse (Promise) est passée à une expression await, celle-ci attend que la promesse (Promise) soit complétée et retourne la valeur complétée.
function resoudreApres2Secondes(x) {
return new Promise((resolve) => {
setTimeout(() => {
resolve(x);
}, 2000);
});
}
async function f1() {
const x = await resoudreApres2Secondes(10);
console.log(x); // 10
}
f1();
Objets semi-promesses
Les objets semi-promesses sont résolus exactement de la même manière que les véritables objets Promise.
async function f2() {
const objetSemiPromesse = {
then(resoudre) {
resoudre("résolue !");
},
};
console.log(await objetSemiPromesse); // "résolue !"
}
f2();
Ils peuvent également être rompus :
async function f2() {
const objetSemiPromesse = {
then(_, rejeter) {
rejeter(new Error("rompue !"));
},
};
await objetSemiPromesse; // Lève l'erreur Error : rompue !
}
f2();
Conversion en promesse
Si la valeur n'est pas une Promise, await la convertit en une Promise complétée et l'attend. L'identité de la valeur attendue ne change pas tant qu'elle ne possède pas de propriété then appelable.
async function f3() {
const y = await 20;
console.log(y); // 20
const obj = {};
console.log((await obj) === obj); // true
}
f3();
Gérer les promesses rompues
Si la promesse (Promise) est rompue, la valeur rompue est levée.
async function f4() {
try {
const z = await Promise.reject(new Error("rompue !"));
} catch (e) {
console.error(e); // Erreur : rompue !
}
}
f4();
Vous pouvez gérer les promesses rompues sans bloc try en chaînant un gestionnaire catch() avant d'attendre la promesse.
const reponse = await fonctionPromesse().catch((erreur) => {
console.error(erreur);
return "réponse par défaut";
});
// reponse vaut « réponse par défaut » si la promesse est rompue
Cette approche repose sur l'hypothèse que fonctionPromesse() ne lève jamais d'erreur de manière synchrone, mais retourne toujours une promesse rompue. C'est le cas de la plupart des fonctions fondées sur les promesses et correctement conçues, qui ressemblent généralement à ceci :
function fonctionPromesse() {
// Retourne immédiatement une promesse pour réduire le risque de lever une erreur
return new Promise((resoudre, rejeter) => {
// Effectuer une opération asynchrone
});
}
Cependant, si fonctionPromesse() lève une erreur de manière synchrone, le gestionnaire catch() ne l'intercepte pas. Dans ce cas, l'instruction try...catch est nécessaire.
await au niveau supérieur
Vous pouvez utiliser le mot-clé await seul, en dehors d'une fonction asynchrone, au niveau supérieur d'un module. Ainsi, les modules qui possèdent des modules enfants utilisant await attendent l'exécution de ces modules enfants avant de s'exécuter eux-mêmes, sans bloquer le chargement des autres modules enfants.
Voici un exemple de module qui utilise l'API Fetch (angl.) et définit await dans l'instruction export. Les modules qui incluent ce module attendent la résolution de la requête avant d'exécuter du code.
// Requête Fetch
const couleurs = fetch("../data/colors.json").then((reponse) => reponse.json());
export default await couleurs;
Observer les effets de await sur le flux
Lorsqu'un await est rencontré dans le code (soit dans une fonction asynchrone, soit dans un module), l'expression attendue est exécutée, tandis que tout le code qui dépend de la valeur de l'expression est mis en pause. Le contrôle quitte la fonction et revient à l'appelant. Lorsque la valeur de l'expression attendue est résolue, une autre micro-tâche qui continue le code en pause est planifiée. Cela se produit même si la valeur attendue est une promesse déjà résolue ou non : l'exécution ne revient pas à la fonction actuelle tant que toutes les autres micro-tâches déjà planifiées ne sont pas traitées. Par exemple, considérez le code suivant :
async function toto(nom) {
console.log(nom, "début");
console.log(nom, "milieu");
console.log(nom, "fin");
}
toto("Première");
toto("Deuxième");
// Première début
// Première milieu
// Première fin
// Deuxième début
// Deuxième milieu
// Deuxième fin
Dans ce cas, la fonction toto est synchrone en pratique, car elle ne contient aucune expression await. Les trois instructions s'exécutent lors du même cycle. Les deux appels de fonction exécutent donc toutes les instructions dans l'ordre. En termes de promesses, la fonction correspond à ceci :
function toto(nom) {
return new Promise((resoudre) => {
console.log(nom, "début");
console.log(nom, "milieu");
console.log(nom, "fin");
resoudre();
});
}
Cependant, dès qu'un await apparaît, la fonction devient asynchrone et l'exécution des instructions suivantes est reportée au cycle suivant.
async function toto(nom) {
console.log(nom, "début");
await console.log(nom, "milieu");
console.log(nom, "fin");
}
toto("Première");
toto("Deuxième");
// Première début
// Première milieu
// Deuxième début
// Deuxième milieu
// Première fin
// Deuxième fin
Cela correspond à ceci :
function toto(nom) {
return new Promise((resoudre) => {
console.log(nom, "début");
resoudre(console.log(nom, "milieu"));
}).then(() => {
console.log(nom, "fin");
});
}
Le gestionnaire then() supplémentaire peut être fusionné avec l'exécuteur transmis au constructeur, car il n'attend aucune opération asynchrone. Cependant, sa présence divise le code en une micro-tâche supplémentaire pour chaque appel de toto. Ces micro-tâches sont planifiées et exécutées de manière entrelacée, ce qui peut ralentir votre code et introduire des conditions de concurrence inutiles. Utilisez donc await uniquement lorsque c'est nécessaire, pour déballer les promesses en leurs valeurs.
Les micro-tâches sont planifiées non seulement lors de la résolution des promesses, mais aussi par d'autres API web, et elles s'exécutent avec la même priorité. Cet exemple utilise queueMicrotask() pour montrer comment la file des micro-tâches est traitée lorsqu'une expression await est rencontrée.
let i = 0;
queueMicrotask(function tester() {
i++;
console.log("micro-tâche", i);
if (i < 3) {
queueMicrotask(tester);
}
});
(async () => {
console.log("début de la fonction asynchrone");
for (let i = 1; i < 3; i++) {
await null;
console.log("reprise de la fonction asynchrone", i);
}
await null;
console.log("fin de la fonction asynchrone");
})();
queueMicrotask(() => {
console.log("queueMicrotask() après l'appel de la fonction asynchrone");
});
console.log("fin de la partie synchrone du script");
// Journalisation :
// début de la fonction asynchrone
// fin de la partie synchrone du script
// opération 1
// reprise de la fonction asynchrone 1
// queueMicrotask() après l'appel de la fonction asynchrone
// opération 2
// reprise de la fonction asynchrone 2
// opération 3
// fin de la fonction asynchrone
Dans cet exemple, la fonction test() est toujours appelée avant la reprise de la fonction asynchrone. Les micro-tâches qu'elles planifient sont donc toujours exécutées de manière entrelacée. En revanche, comme await et queueMicrotask() planifient tous deux des micro-tâches, l'ordre d'exécution dépend toujours de l'ordre de planification. C'est pourquoi le message « queueMicrotask() après l'appel de la fonction asynchrone » apparaît après la première reprise de la fonction asynchrone.
Améliorer la trace de la pile
Parfois, await est omis lorsqu'une promesse est directement retournée par une fonction asynchrone.
async function sansAwait() {
// Certaines actions...
return /* await */ derniereTacheAsynchrone();
}
Cependant, prenons le cas où derniereTacheAsynchrone lève une erreur de manière asynchrone.
async function derniereTacheAsynchrone() {
await null;
throw new Error("échec");
}
async function sansAwait() {
return derniereTacheAsynchrone();
}
sansAwait();
// Erreur : échec
// à derniereTacheAsynchrone
Seule derniereTacheAsynchrone apparaît dans la trace de la pile, car la promesse est rompue après avoir déjà été retournée par sansAwait : d'une certaine manière, la promesse n'est pas liée à sansAwait. Pour améliorer la trace de la pile, vous pouvez utiliser await pour déballer la promesse afin que l'exception soit levée dans la fonction actuelle. L'exception est alors immédiatement enveloppée dans une nouvelle promesse rompue, mais l'appelant apparaît dans la trace de la pile lors de la création de l'erreur.
async function derniereTacheAsynchrone() {
await null;
throw new Error("échec");
}
async function avecAwait() {
return await derniereTacheAsynchrone();
}
avecAwait();
// Erreur : échec
// at derniereTacheAsynchrone
// at async avecAwait
Contrairement à une idée répandue, return await promise est au moins aussi rapide que return promise, grâce à l'optimisation de la résolution des promesses natives par la spécification et les moteurs. Une proposition vise à rendre return promise plus rapide (angl.), et vous pouvez également lire l'article sur l'optimisation de V8 concernant les fonctions asynchrones (angl.). Ainsi, sauf pour des raisons stylistiques, return await est presque toujours préférable.
Spécifications
| Spécification |
|---|
| ECMAScript® 2027 Language Specification> # sec-async-function-definitions> |
Compatibilité des navigateurs
Voir aussi
- L'instruction
async function - L'expression
async function - L'objet natif
AsyncFunction awaitde niveau supérieur (angl.) sur v8.dev (2019)- Règle typescript-eslint :
return-await(angl.)