JavaScript Asynchrone

JavaScript Asynchrone

JavaScript exécute une seule chose à la fois, et pourtant, il ne freeze jamais en attendant. L'event loop, les callbacks, les promises et async/await sont les mécanismes qui rendent cela possible.

intermediate16 min readUpdated 15 sept. 2026
js
// user.js
async function loadUser(id) {
  const res = await fetch(`/api/users/${id}`);
  if (!res.ok) throw new Error("User not found");
  return res.json();
}

const user = await loadUser(42);
console.log(user.name);
Modèle d'exécution
Event loop mono-thread
Première solution
Callbacks
Solution moderne
Promises + async/await
États d'une Promise
pending, fulfilled, rejected
Exécution parallèle
Promise.all / allSettled
Mots-clés
async / await

Pratique

Observez l'event loop

L'ordre des messages montre comment l'event loop planifie le travail.

Observez l'event loop

L'ordre des messages montre comment l'event loop planifie le travail.

Pourquoi c'est important

Pourquoi l'asynchrone est essentiel

Ne bloque jamais la page

Les tâches lentes s'exécutent en dehors du thread principal tandis que l'UI reste réactive, évitant ainsi que l'utilisateur ne se retrouve face à un onglet figé.

Se lit comme du code synchrone

async/await vous permet d'écrire une logique asynchrone de haut en bas, sans imbriquer de callbacks ni chaîner des blocs then.

Conçu pour le réseau

Chaque fetch, timer, lecture de fichier et appel à une base de données en JavaScript moderne est asynchrone ; c'est donc un concept central et non optionnel.

Le tableau complet

Les trois pièces du puzzle

Le JavaScript asynchrone n'est pas une seule fonctionnalité, mais trois couches superposées : l'event loop, les promises et une syntaxe qui se lit comme du code synchrone.

L'event loop

Ordonnancement

Une call stack, une file d'attente de tâches (task queue) et une file de microtâches (microtask queue) décident de l'ordre réel d'exécution de votre code.

Promises

Représentation

Un objet qui représente une valeur future, offrant un moyen propre de chaîner des opérations et de gérer les échecs.

async / await

Syntaxe

Une fine couche au-dessus des promises qui rend le code asynchrone visuellement identique à du code synchrone ordinaire.

L'asynchrone en un coup d'œil

Les outils de votre boîte à outils async

L'event loop

Exécute les tâches en file d'attente une fois que le code synchrone actuel est terminé.

Callbacks

Le pattern original : passer une fonction à exécuter lorsque le travail est terminé.

Promises

Objets à trois états permettant de chaîner des gestionnaires de succès et d'échec.

Combinateurs

all, allSettled, race et any coordonnent plusieurs promises simultanément.

async / await

Met en pause une fonction au niveau du await sans bloquer le reste du programme.

Gestion des erreurs

catch et try/catch transforment les promises rejetées en erreurs normales et gérables.

Un bref aperçu

Des callbacks à async/await

  1. 2005

    AJAX popularise les callbacks

    Gmail et Google Maps montrent au monde que les pages peuvent se mettre à jour sans rechargement.

    05
  2. 2009

    Node.js adopte l'asynchrone

    Le JavaScript côté serveur fait des E/S non bloquantes la méthode par défaut pour construire des backends.

    09
  3. 2015

    Promises dans ES6

    L'objet Promise est standardisé, offrant un contrat commun pour le code asynchrone.

    15
  4. 2017

    async / await

    ES2017 ajoute la syntaxe qui permet au code asynchrone de se lire comme du code synchrone.

    17
  5. Aujourd'hui

    L'asynchrone partout

    Fetch, les streams, les workers et la plupart des API modernes sont basés sur les promises par défaut.

    Aujourd'hui

Le guide complet

JavaScript Asynchrone: Tout ce que vous devez savoir

Pourquoi le JavaScript asynchrone existe

Les ordinateurs effectuent certaines tâches lentement : lire des fichiers, interroger des bases de données ou attendre la réponse d’un serveur. Si JavaScript s’arrêtait pour attendre chaque opération, la page gèlerait et les utilisateurs quitteraient le site. La programmation asynchrone est la méthode qui permet au langage de continuer à fonctionner pendant que des tâches lentes s’exécutent ailleurs.

Le modèle mental à adopter est celui d’un restaurant. Un seul serveur prend votre commande, la transmet à la cuisine, puis sert immédiatement une autre table. Lorsque votre plat est prêt, la cuisine sonne une cloche et le serveur vous l’apporte. Le serveur ne reste jamais dans la cuisine à regarder la nourriture cuire. JavaScript est ce serveur, et l’event loop est la cloche.

La pile d’appels et la boucle d’événements

JavaScript possède une seule call stack (pile d’appels). Chaque appel de fonction pousse une frame ; lorsqu’elle retourne, la frame est dépilée. Comme il n’y a qu’une seule pile, une seule opération peut s’exécuter à la fois.

Les API asynchrones sont fournies par l’environnement hôte, et non par le langage lui-même. Lorsque vous appelez setTimeout, fetch ou une lecture de fichier, le navigateur ou Node.js gère l’attente. Une fois le travail terminé, son callback est placé dans une file d’attente. L’event loop (boucle d’événements) vérifie constamment : la call stack est-elle vide ? Si oui, elle récupère le callback suivant de la file et l’exécute.

// order.js
console.log("start");

setTimeout(() => console.log("timeout"), 0);

Promise.resolve().then(() => console.log("promise"));

console.log("end");

// start, end, promise, timeout

Deux détails sont importants. Premièrement, le code synchrone se termine toujours avant l’exécution de tout callback. Deuxièmement, il existe deux files d’attente : les microtasks (callbacks de promesses) s’exécutent immédiatement après la tâche actuelle, avant les tasks (timers, E/S). C’est pourquoi promise s’affiche dans les logs avant timeout.

Callbacks

La méthode originelle pour gérer les résultats asynchrones est le callback — une fonction que l’on passe en argument pour qu’elle soit appelée plus tard.

// callback.js
function getUser(id, callback) {
  setTimeout(() => {
    callback({ id, name: "Ada" });
  }, 300);
}

getUser(1, (user) => {
  console.log(user.name);
});

Les callbacks fonctionnent, mais ils se composent mal. Lorsque chaque étape dépend de la précédente, on se retrouve à imbriquer les fonctions, ce qui mène au callback hell : un code qui s’étire en diagonale sur l’écran et rend la gestion des erreurs pénible.

// hell.js
getUser(1, (user) => {
  getPosts(user.id, (posts) => {
    getComments(posts[0].id, (comments) => {
      // three levels deep and counting
    });
  });
});

Promises

Une Promise est un objet qui représente une valeur qui peut ne pas encore exister. Elle se trouve dans l’un des trois états suivants :

  • pending — l’opération est toujours en cours.
  • fulfilled — l’opération s’est terminée avec succès et a retourné une valeur.
  • rejected — l’opération a échoué pour une raison donnée.

Une fois stabilisée (settled), une promise ne change plus jamais d’état. Vous y attachez des gestionnaires via then, catch et finally.

// promise.js
function getUser(id) {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (id > 0) resolve({ id, name: "Ada" });
      else reject(new Error("Invalid id"));
    }, 300);
  });
}

getUser(1)
  .then((user) => console.log(user.name))
  .catch((error) => console.error(error.message))
  .finally(() => console.log("done"));

Le chaînage est son principal avantage. Chaque then retourne une nouvelle promise ; ainsi, au lieu d’imbriquer vos appels, vous pouvez aplatir la séquence et gérer toutes les erreurs dans un seul catch à la fin.

// chain.js
getUser(1)
  .then((user) => getPosts(user.id))
  .then((posts) => getComments(posts[0].id))
  .then((comments) => console.log(comments))
  .catch((error) => console.error("Something failed:", error));

Combiner des promesses

Lorsque plusieurs opérations asynchrones sont impliquées, le constructeur Promise propose des combinateurs.

// combine.js
const [user, posts, settings] = await Promise.all([
  getUser(1),
  getPosts(1),
  getSettings(),
]);

const results = await Promise.allSettled([getUser(1), getUser(-1)]);

const fastest = await Promise.race([getFromCache(), getFromNetwork()]);

const firstSuccess = await Promise.any([mirrorA(), mirrorB()]);

Promise.all exécute tout en parallèle et rejette si l’une des promesses est rejetée. Promise.allSettled attend la fin de toutes les opérations et rapporte chaque résultat. Promise.race se termine dès que la première promesse est résolue ou rejetée, et Promise.any se résout avec la première promesse réussie. Le choix du combinateur modifie radicalement la gestion de vos erreurs.

async et await

async et await sont des syntaxes construites au-dessus des promesses. Marquer une fonction comme async fait en sorte qu’elle retourne toujours une promesse. À l’intérieur, await met la fonction en pause jusqu’à ce qu’une promesse soit résolue, puis reprend avec la valeur résolue — sans bloquer le reste du programme.

// await.js
async function loadDashboard() {
  try {
    const user = await getUser(1);
    const posts = await getPosts(user.id);
    return { user, posts };
  } catch (error) {
    console.error("Failed to load dashboard", error);
    throw error;
  }
}

Cela se lit comme du code synchrone, et c’est tout l’intérêt. Les erreurs sont gérées avec des blocs try/catch classiques, et finally continue de s’exécuter. La fonction retournant une promesse, les appelants peuvent l’await ou chaîner des .then comme d’habitude.

Séquentiel vs parallèle

Une erreur subtile mais coûteuse consiste à attendre des tâches indépendantes une par une. Chaque await attend la précédente, le temps total est donc la somme de toutes les attentes. Lorsque les tâches ne dépendent pas les unes des autres, lancez-les simultanément.

// parallel.js
// Sequential — slow, ~600ms
const a = await slowTask("a");
const b = await slowTask("b");

// Parallel — fast, ~300ms
const [x, y] = await Promise.all([slowTask("x"), slowTask("y")]);

N’utilisez des await séquentiels que lorsqu’une étape ultérieure a réellement besoin du résultat d’une étape précédente, comme récupérer un utilisateur avant de récupérer les posts de cet utilisateur.

Gestion des erreurs dans le code asynchrone

Les promesses rejetées qui ne sont traitées par personne deviennent des unhandled rejections, ce qui peut faire planter les processus Node.js et encombrer la console dans les navigateurs.

// errors.js
try {
  const res = await fetch("/api/data");
  if (!res.ok) throw new Error(`HTTP ${res.status}`);
  const data = await res.json();
} catch (error) {
  showMessage("Could not load data. Please try again.");
} finally {
  hideSpinner();
}

Privilégiez l’utilisation de try/catch autour des appels attendus avec await, lancez des objets Error (et non des chaînes de caractères), et traitez toujours l’erreur ou relancez-la délibérément. Le guide de gestion des erreurs approfondit les stratégies et la communication des messages aux utilisateurs.

Pièges courants

  • Oublier await. La fonction retourne une promesse et non la valeur, donc la ligne suivante s’exécute trop tôt.
  • forEach avec des callbacks asynchrones. forEach ignore les promesses retournées ; utilisez for...of ou Promise.all avec map.
  • Utiliser await dans une boucle alors que le parallélisme suffirait. Cela fonctionne, mais c’est inutilement lent.
  • Ne pas retourner de valeur à l’intérieur de then. L’absence de return brise la chaîne et fait perdre la valeur.
  • Supposer l’ordre d’exécution. Seuls await et l’ordonnancement des microtâches garantissent la séquence ; les tâches concurrentes se terminent quand elles le peuvent.
  • Ignorer les erreurs avec un catch vide, masquant ainsi le bug que vous auriez dû identifier.

Bonnes pratiques

  • Privilégiez async/await aux longues chaînes de .then pour une meilleure lisibilité.
  • Exécutez les tâches indépendantes en parallèle avec Promise.all ou Promise.allSettled.
  • Gérez toujours les rejets, ne serait-ce que pour les journaliser et les relancer.
  • Gardez vos fonctions async courtes et assignez-leur une seule responsabilité.
  • Utilisez Promise.allSettled lorsque des résultats partiels sont acceptables.
  • Annulez le travail dont vous n’avez plus besoin avec un AbortController.
  • Affichez des états de chargement et d’erreur pour que les utilisateurs comprennent ce qu’il se passe.

Et après ?

Vous disposez désormais de l’ensemble des outils asynchrones. Mettez-les en pratique avec l’ Fetch API, où les promesses et await pilotent chaque requête réseau, et renforcez votre code en appliquant les modèles de Gestion des erreurs.

Chaînage vs await

async/await supprime les callbacks imbriqués et rend la séquence évidente.

Préférer
async function load() {
  const user = await getUser(1);
  const posts = await getPosts(user.id);
  return posts;
}
Éviter
function load() {
  return getUser(1).then((user) => {
    return getPosts(user.id).then((posts) => {
      return posts;
    });
  });
}

Travail indépendant

Si les requêtes ne dépendent pas les unes des autres, lancez-les ensemble avec Promise.all.

Préférer
const [user, posts] = await Promise.all([
  getUser(1),
  getPosts(1),
]);
Éviter
const user = await getUser(1);
const posts = await getPosts(1);
// runs one after the other

FAQ

Foire aux questions

Keep learning

Related topics from the roadmap.

$ commencer à apprendre

Prêt à apprendre Async JavaScript ?

Notre tutoriel interactif vous guide à travers Async JavaScript pas à pas — avec des quiz et du vrai code que vous pouvez exécuter dans le navigateur.