Accueil / javascript / 1-js / 11-async / 08-async-await / article.md

article.md

Async/await

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.

Prenons l'exemple showAvatar() du chapitre <info:promise-chaining> et réécrivons-le en utilisant async/await:

  1. Nous devons remplacer les appels .then par await.
  2. Aussi, nous devrions faire la fonction async pour 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.

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 <info:promise-error-handling>.

async/await et promise.then/catch
Lorsque 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.

Résumé

Le mot-clé async devant une fonction a deux effets:

  1. Fait en sorte qu'elle retourne toujours une promesse.
  2. Permet l'utilisation de await dans celle-ci.

Le mot-clé await devant une promesse fait en sorte que JavaScript attende jusqu'à ce que cette promesse se règle, puis:

  1. Si c'est une erreur, l'exception est générée - comme si throw error était appelé à cet endroit précis.
  2. 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.