Créa-blog

#100JoursPourCoder
Projet Créa-code

Ressources pour développeur web

Async/Await vs Promises en JS : explications et exemples

⏱️ Temps de lecture estimé : 9 minutes
Accueil Javascript Async/Await vs Promises en JS : explications et exemples

Lorsque l’on apprend le JavaScript, on se rend rapidement compte qu’une grande partie de ce langage repose sur la gestion de tâches asynchrones. Que ce soit pour interroger une API, lire un fichier, attendre une réponse d’un serveur ou tout simplement temporiser une action, l’asynchrone est omniprésent. Pourtant, pour les débutants comme pour les développeurs confirmés, comprendre et maîtriser les outils permettant de gérer ces opérations peut s’avérer complexe. Deux notions se détachent particulièrement dans ce domaine : les Promises et la syntaxe async/await.

Ces deux mécanismes ont le même objectif, mais leur approche diffère. Beaucoup se posent alors les mêmes questions : qu’est-ce qu’une Promise ? Pourquoi async/await a-t-il été introduit ? Quand privilégier l’un ou l’autre ?

Dans ce tutoriel, nous allons répondre en détail à toutes ces questions. L’objectif est de vous donner une compréhension claire, pratique et complète de ces deux concepts, avec des explications simples et de nombreux exemples concrets. Vous pourrez ainsi savoir lequel utiliser dans vos propres projets, et surtout comprendre pourquoi vous faites ce choix.

Qu’est-ce que l’asynchronisme en JavaScript ?

Avant d’entrer dans le vif du sujet, il est essentiel de comprendre ce que signifie réellement le mot asynchrone.

En JavaScript, le code est exécuté ligne par ligne, de manière séquentielle. C’est ce qu’on appelle l’exécution synchrone. Mais certaines opérations prennent plus de temps que d’autres. Par exemple, une requête HTTP envoyée à un serveur mettra plusieurs millisecondes, voire plusieurs secondes, à obtenir une réponse. Si le langage attendait la fin de cette requête avant de continuer, le programme serait bloqué, ce qui est évidemment inacceptable dans un environnement interactif comme un site web.

C’est ici qu’intervient le modèle asynchrone. Au lieu d’attendre qu’une tâche longue se termine, le JavaScript délègue cette tâche, continue l’exécution du reste du code, puis revient vers la tâche une fois qu’elle est terminée. On parle de non-blocking code.

Pour illustrer cela, imaginez que vous commandiez un café dans un bar. Si le serveur fonctionnait en mode synchrone, il resterait immobile devant la machine à café jusqu’à ce que votre boisson soit prête, empêchant toute autre commande. En mode asynchrone, il prend votre commande, lance la préparation, puis continue à servir d’autres clients en attendant que le café soit prêt.

C’est exactement le rôle des Promises et de async/await : gérer de manière claire et efficace ce type de situation.

Les Promises en JavaScript : définition et fonctionnement

Les Promises ont été introduites dans le langage pour résoudre un problème bien connu des développeurs : le fameux callback hell.

Avant les Promises, l’asynchronisme en JavaScript était géré uniquement avec des fonctions de rappel (callbacks). Cela fonctionnait, mais entraînait souvent un code difficile à lire et à maintenir, surtout lorsque plusieurs opérations dépendaient les unes des autres.

Une Promise est un objet qui représente la valeur d’une opération asynchrone, valeur qui peut être disponible maintenant, plus tard, ou jamais. Une Promise peut être dans trois états :

  • Pending (en attente) : l’opération est en cours.
  • Fulfilled (réussie) : l’opération s’est terminée avec succès et une valeur est disponible.
  • Rejected (échouée) : l’opération a rencontré une erreur.

Exemple simple avec une Promise

const maPromise = new Promise((resolve, reject) => {
    const succes = true;

    if (succes) {
        resolve("L'opération a réussi !");
    } else {
        reject("Une erreur est survenue...");
    }
});

maPromise
    .then(resultat => {
        console.log(resultat);
    })
    .catch(erreur => {
        console.error(erreur);
    });

Dans cet exemple, nous créons une Promise qui peut soit être résolue, soit rejetée. La méthode .then() permet de traiter le cas où tout se passe bien, et .catch() gère l’erreur.

Ce fonctionnement apporte déjà une grande amélioration par rapport aux callbacks imbriqués. Le code est plus lisible, plus structuré et les erreurs sont mieux gérées.

Les limites des Promises

Bien que les Promises aient largement simplifié la gestion de l’asynchronisme, elles présentent certaines limites.

Premièrement, lorsqu’il faut enchaîner plusieurs opérations asynchrones qui dépendent les unes des autres, on se retrouve rapidement avec des chaînes de .then() successifs. Certes, cela reste plus clair que les callbacks imbriqués, mais le code peut devenir long et difficile à suivre.

Deuxièmement, la syntaxe peut sembler lourde pour les débutants. Même si elle devient intuitive avec la pratique, il reste une courbe d’apprentissage à franchir.

C’est pour répondre à ces problématiques qu’a été introduite la syntaxe async/await, qui s’appuie sur les Promises mais propose une approche plus naturelle.

Async/Await en JavaScript : définition et fonctionnement

La syntaxe async/await est apparue officiellement avec ECMAScript 2017. Elle a été conçue pour rendre le code asynchrone plus simple à écrire et surtout plus lisible.

Contrairement à ce que l’on pourrait croire, async/await ne remplace pas les Promises, il repose sur elles. Autrement dit, utiliser async/await revient à manipuler des Promises, mais avec une syntaxe différente et souvent plus claire.

L’idée est d’écrire du code asynchrone qui ressemble fortement à du code synchrone. Cela permet de suivre plus facilement le déroulement logique d’un programme, sans se perdre dans des chaînes de .then() ou des callbacks.

La fonction async

Lorsqu’on déclare une fonction avec le mot-clé async, cela signifie que cette fonction retournera toujours une Promise, même si l’on n’utilise pas explicitement le mot Promise à l’intérieur.

Voici un exemple :

async function maFonction() {
    return "Bonjour avec async !";
}

maFonction().then(resultat => console.log(resultat));

Dans ce cas, la fonction maFonction retourne automatiquement une Promise résolue avec la valeur "Bonjour avec async !".

Cela signifie que l’on peut utiliser .then() pour récupérer son résultat, comme avec n’importe quelle Promise.

Le mot-clé await

Le mot-clé await est utilisé à l’intérieur d’une fonction async. Il indique à JavaScript d’attendre que la Promise soit résolue avant de continuer l’exécution de la fonction.

Prenons un exemple concret :

function attendre(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function exemple() {
    console.log("Début");

    await attendre(2000);

    console.log("Fin après 2 secondes");
}

exemple();

Dans ce code, la fonction attendre retourne une Promise qui se résout après un certain temps. Grâce au mot await, la fonction exemple met en pause son exécution pendant 2 secondes avant de continuer.

Le grand avantage est la clarté du code : on lit la fonction de haut en bas, sans avoir besoin d’imaginer des enchaînements de .then().

Exemple comparatif Promises vs Async/Await

Pour bien comprendre la différence de syntaxe, comparons un cas simple : récupérer des données depuis une API.

Avec Promises

fetch("https://jsonplaceholder.typicode.com/posts/1")
    .then(response => response.json())
    .then(data => {
        console.log("Données reçues :", data);
    })
    .catch(erreur => {
        console.error("Erreur :", erreur);
    });

Avec Async/Await

async function getData() {
    try {
        const response = await fetch("https://jsonplaceholder.typicode.com/posts/1");
        const data = await response.json();
        console.log("Données reçues :", data);
    } catch (erreur) {
        console.error("Erreur :", erreur);
    }
}

getData();

Les deux exemples font exactement la même chose. Mais dans la version async/await, la lecture est beaucoup plus fluide. On voit immédiatement les étapes successives : on attend la réponse, puis on attend la conversion en JSON, puis on affiche les données.

Formation web et informatique - Alban Guillier - Formateur

Des formations informatique pour tous !

Débutant ou curieux ? Apprenez le développement web, le référencement, le webmarketing, la bureautique, à maîtriser vos appareils Apple et bien plus encore…

Formateur indépendant, professionnel du web depuis 2006, je vous accompagne pas à pas et en cours particulier, que vous soyez débutant ou que vous souhaitiez progresser. En visio, à votre rythme, et toujours avec pédagogie.

Découvrez mes formations Qui suis-je ?

Les avantages d’async/await

Async/await présente plusieurs bénéfices notables :

  1. Lisibilité accrue : le code se lit comme du code synchrone, ce qui le rend beaucoup plus compréhensible, surtout pour les débutants.
  2. Gestion des erreurs simplifiée : au lieu de chaîner plusieurs .catch(), on peut simplement utiliser un bloc try/catch.
  3. Debugging plus facile : les erreurs sont plus faciles à suivre, car les fonctions async affichent des traces plus claires dans la console.

Les limites d’async/await

Malgré ses avantages, async/await n’est pas une solution magique. Il y a quelques points à garder en tête :

  • Exécution séquentielle forcée : par défaut, utiliser plusieurs await dans une fonction entraîne une exécution étape par étape, même si certaines opérations pourraient être réalisées en parallèle.
  • Toujours basé sur les Promises : il faut comprendre que async/await ne supprime pas les Promises, il ne fait que les rendre plus agréables à utiliser.
  • Compatibilité initiale : au moment de son introduction, certains environnements ne supportaient pas async/await. Aujourd’hui, cela ne pose plus de problème avec les navigateurs modernes.

Exemple d’exécution séquentielle vs parallèle

Voici un exemple qui illustre l’un des pièges courants avec async/await :

function attendre(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
}

async function executionSequentielle() {
    console.log("Début séquentiel");
    await attendre(2000);
    await attendre(2000);
    console.log("Fin séquentielle (après 4 secondes)");
}

async function executionParallele() {
    console.log("Début parallèle");
    await Promise.all([attendre(2000), attendre(2000)]);
    console.log("Fin parallèle (après 2 secondes)");
}

executionSequentielle();
executionParallele();

Dans le premier cas, les deux attentes de 2 secondes s’additionnent, car elles s’exécutent l’une après l’autre. Dans le second, elles sont lancées en parallèle grâce à Promise.all, et la durée totale est seulement de 2 secondes.

Cela montre bien qu’il faut rester attentif à la logique d’exécution quand on manipule async/await.

Async/Await ou Promises : lequel choisir et quand ?

Maintenant que nous avons exploré les deux mécanismes, il est important de comprendre quand utiliser l’un ou l’autre. Il n’y a pas de réponse unique, car tout dépend du contexte et du type de projet.

Quand privilégier les Promises

Les Promises sont particulièrement utiles lorsque vous devez gérer des opérations simples et indépendantes, ou lorsque vous souhaitez chaîner plusieurs actions de manière concise. Elles sont idéales pour :

  • Effectuer des opérations asynchrones courtes et isolées.
  • Enchaîner des traitements qui ne nécessitent pas forcément une lecture ligne par ligne.
  • Écrire des librairies ou des modules qui doivent rester compatibles avec différents environnements.

Par exemple, si vous devez faire plusieurs requêtes réseau qui ne dépendent pas les unes des autres, une chaîne de .then()peut être parfaitement adaptée.

Quand privilégier async/await

Async/await devient la solution de choix lorsque :

  • Vous souhaitez écrire du code asynchrone qui se lit comme du code synchrone.
  • Vous devez effectuer plusieurs opérations asynchrones dépendantes les unes des autres.
  • La lisibilité et la facilité de maintenance sont prioritaires, surtout dans des projets de grande envergure.

Async/await simplifie également la gestion des erreurs grâce aux blocs try/catch, ce qui rend votre code plus robuste et moins sujet aux oublis dans le traitement des exceptions.

Comparaison synthétique

Si l’on devait résumer :

  • Les Promises sont parfaites pour les tâches courtes, indépendantes et rapides à écrire.
  • Async/await est idéal pour des séquences longues, des opérations dépendantes et un code lisible et maintenable.

L’important est de comprendre que async/await repose sur les Promises. Toute fonction async retourne une Promise, et await attend qu’une Promise soit résolue. Ainsi, il ne s’agit pas de choisir entre deux mondes différents, mais plutôt de choisir la syntaxe la plus adaptée à votre cas.

Cas concrets d’utilisation

Pour mieux comprendre l’usage réel de ces concepts, voici deux exemples concrets :

Cas 1 : Appel à une API pour récupérer des données

Imaginons que vous construisiez un site météo et que vous deviez récupérer les prévisions depuis un serveur distant. Avec async/await, le code pourrait ressembler à ceci :

async function obtenirMeteo(ville) {
    try {
        const response = await fetch(`https://api.meteo.com/previsions/${ville}`);
        const data = await response.json();
        console.log(`Météo pour ${ville} :`, data);
    } catch (erreur) {
        console.error("Erreur lors de la récupération des données météo :", erreur);
    }
}

obtenirMeteo("Paris");

Ici, chaque étape est claire : on envoie la requête, on attend la réponse, on convertit le résultat en JSON, puis on l’affiche. En cas d’erreur, le bloc catch l’attrape directement.

Avec les Promises, le même processus serait moins lisible, surtout si vous enchaînez plusieurs traitements.

Cas 2 : Enchaînement de plusieurs opérations asynchrones dépendantes

Supposons que vous développiez une application de gestion de commandes. Vous devez :

  1. Vérifier l’inventaire d’un produit.
  2. Créer une commande si le produit est disponible.
  3. Envoyer un email de confirmation au client.

Avec async/await, vous pourriez écrire :

async function traiterCommande(produit, client) {
    try {
        const stock = await verifierStock(produit);
        if (stock > 0) {
            const commande = await creerCommande(produit, client);
            await envoyerEmailConfirmation(client, commande);
            console.log("Commande traitée avec succès !");
        } else {
            console.log("Produit en rupture de stock.");
        }
    } catch (erreur) {
        console.error("Erreur lors du traitement de la commande :", erreur);
    }
}

traiterCommande("ordinateur", "[email protected]");

Dans cet exemple, chaque étape dépend de la précédente. Async/await rend la logique très claire et linéaire, exactement comme si vous écriviez du code synchrone.

Si vous utilisiez des Promises avec des .then(), le code serait beaucoup plus imbriqué et moins facile à lire, surtout pour les débutants ou pour des séquences plus longues.

Astuce : combiner Promises et async/await

Il est possible et souvent pratique de combiner les deux approches. Par exemple, vous pouvez utiliser Promise.all avec async/await pour exécuter plusieurs tâches en parallèle :

async function traitementParallele() {
    const taches = [
        fetch("/api/utilisateur"),
        fetch("/api/produits"),
        fetch("/api/commandes")
    ];

    try {
        const [utilisateur, produits, commandes] = await Promise.all(taches.map(t => t.then(r => r.json())));
        console.log(utilisateur, produits, commandes);
    } catch (erreur) {
        console.error("Erreur lors du traitement parallèle :", erreur);
    }
}

traitementParallele();

Cela montre qu’il n’y a pas de conflit entre Promises et async/await : au contraire, l’association des deux permet d’optimiser à la fois la lisibilité et la performance.


Dans ce tutoriel, nous avons exploré en profondeur deux mécanismes essentiels pour gérer l’asynchronisme en JavaScript : les Promises et la syntaxe async/await. Vous savez désormais ce qu’ils sont, comment ils fonctionnent, leurs avantages, leurs limites, ainsi que les situations dans lesquelles il est préférable d’utiliser l’un ou l’autre.

Les Promises ont été conçues pour simplifier la gestion des opérations asynchrones et résoudre le problème du callback hell. Elles sont particulièrement adaptées pour les opérations indépendantes ou pour des tâches rapides et simples. Elles permettent d’enchaîner plusieurs traitements de manière organisée et de gérer facilement les erreurs grâce à .catch().

Async/await, en revanche, repose sur les Promises mais apporte une lisibilité accrue. Son principal avantage est de rendre le code asynchrone quasi identique à du code synchrone, ce qui facilite sa lecture et sa maintenance. Grâce aux blocs try/catch, la gestion des erreurs est centralisée et intuitive, et le code devient plus clair même dans les séquences complexes où chaque opération dépend de la précédente.

Nous avons également montré que ces deux approches ne s’excluent pas mutuellement. Il est possible, et souvent conseillé, de combiner async/await et Promises pour tirer parti de leurs forces respectives : lisibilité et parallélisation des opérations. Les exemples concrets, tels que la récupération de données depuis une API ou l’enchaînement d’opérations dépendantes comme la gestion d’une commande, illustrent parfaitement l’efficacité de ces techniques dans des situations réelles.

En résumé, voici quelques recommandations pratiques :

  1. Pour des opérations simples et indépendantes, utilisez des Promises classiques.
  2. Pour des séquences d’opérations dépendantes ou longues, préférez async/await pour la lisibilité et la maintenance.
  3. N’hésitez pas à combiner les deux approches lorsque vous souhaitez paralléliser certaines tâches tout en gardant un code clair.
  4. Gardez toujours à l’esprit la gestion des erreurs avec .catch() ou try/catch pour rendre votre code robuste.

En maîtrisant ces concepts, vous pourrez écrire des applications JavaScript modernes, performantes et faciles à maintenir. Que vous développiez des sites web interactifs, des applications Node.js ou des projets complexes, la compréhension et l’utilisation efficace de Promises et async/await sont désormais des compétences essentielles pour tout développeur JavaScript.

Enfin, n’oubliez pas que l’asynchronisme est une partie centrale du langage. Plus vous pratiquerez avec des exemples concrets, plus vous deviendrez à l’aise pour gérer des tâches complexes et optimiser vos applications. Avec ce tutoriel, vous disposez désormais d’une base solide pour exploiter au mieux ces outils et rendre votre code JavaScript à la fois performant et lisible.

Live on Twitch