Aller au contenu principal

Les promesses

Définition

Nous avons déjà utilisé des promesses sans le savoir lorsque nous avons manipulé fetch l'année dernière.

fetch(url, options)
//pour faire le premier .then, on attend que la promesse renvoyée par fetch soit résolue
//avec la réponse du serveur.
.then(response => response.json())
//pout faire le second .then, on attend que le précédent soit résolu (et ainsi de suite)
.then(result => /* process result */)
.catch(error => {
console.error('Error:', error);
});
// le console.log("coucou"); sera exécuté immédiatement après le lancement de la requête fetch(),
// sans attendre la fin de la requête.
console.log("coucou");

Une autre façon d'écrire cet enchainement de codes asynchrones consiste à utiliser await. De cette façon, le code patiente jusqu'à ce que la promesse soit réglée et la valeur de résolution de la promesse est fournie comme valeur de retour, ou alors la valeur d'échec déclenche une erreur.

try {
let response = await fetch(url, options); // se résout avec des en-têtes de réponse
// La ligne ci-dessous ne sera exécutée que si la réponse est arrivée.
let result = await response.json(); // lit le corps en tant que JSON
// La ligne ci-dessous ne sera exécutée que si la réponse est arrivée.
console.log("coucou");
} catch (error) {
console.error('Error:', error);
}

Définition

Une promesse est une fonction à laquelle on passera deux arguments : resolve et reject. Cette fonction est exécutée immédiatement par l'implémentation de Promise qui fournit les fonctions resolve et reject (elle est exécutée avant que le constructeur Promise ait renvoyé l'objet créé). Les fonctions resolve et reject, lorsqu'elles sont appelées, permettent respectivement de tenir ou de rompre la promesse. On attend de l'exécuteur qu'il démarre un travail asynchrone puis, une fois le travail terminé, appelle la fonction resolve (si tout s'est bien passé) ou la fonction reject (lorsqu'il y a eu un problème) pour définir l'état final de la promesse. Si une erreur est générée par l'exécuteur, la promesse est rompue et la valeur de retour de l'exécuteur est ignorée. (définition mozilla). Le schéma ci-dessous détaille les différents états d'une promesse.

Coder ses propres fonctions asynchrones

Vous aurez parfois besoin d'écrire vos propres fonctions asynchrones car (entre autres) :

  • Les fonctions asynchrones peuvent être combinées et composées plus facilement en utilisant des promesses. Vous pouvez chainer des opérations asynchrones avec des .then() et gérer plusieurs promesses avec des méthodes telles que Promise.all().
  • elles sont compatibles avec async/await : l'utilisation de promesses est la base de l'utilisation des mots-clés async et await, qui simplifient davantage la syntaxe des fonctions asynchrones. async permet de déclarer une fonction asynchrone, et await permet d'attendre la résolution d'une promesse à l'intérieur de cette fonction.
  const promise1 = new Promise(function(resolve, reject) {
//L'asynchronisme est simulé par le timeout aléatoire.
//dans un exemple réel, ce pourrait être un fetch avec une réponse
// un peu lente de la part du serveur
setTimeout(function() {
resolve('foo');
}, Math.random() * 2000 + 1000);
});

//Quand la promesse est tenue c'est ce code (ce qui suit then) qui sera exécuté. resolve est "lié" à then.
promise1.then(function(value) {
console.log(value);
// "foo"
});
//exécution de la promesse
promise1;

Le même code avec await et sync:

const promise2 = new Promise((resolve, reject) => {
// L'asynchronisme est simulé par le timeout aléatoire.
setTimeout(() => {
resolve('foo');
}, Math.random() * 2000 + 1000);
});

const executePromise = async () => {
try {
// Utiliser await pour attendre que la promesse soit résolue
const value = await promise2;
console.log(value);
// expected output: "foo"
} catch (error) {
console.error('Error:', error);
}
};

// Appeler la fonction asynchrone
executePromise();

Dans l'exemple ci-dessous, on écrit un code qui gère un rejet de la promesse.

const methode1 = () => {

return new Promise((resolve, reject) => {
//peut être un chargement d'une grosse image, d'un script, une requette XHR
console.log("Methode 1 fait un truc asynchrone");

// réussir une fois sur deux
if (Math.random() > .5) {
resolve("Tout va bien dans la méthode asynchrone 1");
} else {
reject(Error('Problème méthode 1'));
}
})
}
const methode2 = (reponse) => {
console.log(reponse);
return new Promise((resolve) => {
//peut être un chargement d'une grosse image, d'un script, une requette XHR
console.log("Methode 2 fait un truc asynchrone");
// 100% de réussite
resolve("Tout va bien dans la méthode asynchrone 2");

})
}


methode1()
.then((reponse) => methode2(reponse))
.then((reponse)=> console.log(reponse,"!"))
.catch((alert)=>console.log(alert))
.then(() => console.log ("tout est terminé"));

Lorsque vous utilisez async/await, la fonction elle-même retourne une promesse. Si la fonction retourne une valeur, la promesse est résolue avec cette valeur. Si la fonction lance une exception, la promesse est rejetée avec l'exception lancée.

const methode1 = async () => {
console.log("Methode 1 fait un truc asynchrone");

if (Math.random() > 0.5) {
return "Tout va bien dans la méthode asynchrone 1";
} else {
throw new Error('Problème méthode 1');
}
};

const methode2 = async (reponse) => {
console.log(reponse);
console.log("Methode 2 fait un truc asynchrone");
return "Tout va bien dans la méthode asynchrone 2";
};

const main = async () => {
try {
const reponse1 = await methode1();
const reponse2 = await methode2(reponse1);
console.log(reponse2, "!");
} catch (error) {
console.error(error.message);
} finally {
console.log("tout est terminé");
}
};

// Appeler la fonction principale
main();

Exemple complet montrant l'importance de faire une fonction asynchrone

Nous allons ici utiliser l'API , où vous devez attendre une réponse avant de procéder à d'autres actions. Voici un exemple intéressant qui illustre l'utilisation des promesses pour rendre une fonction asynchrone et justifie pourquoi cela est nécessaire.

Cas d'Utilisation : Récupérer des Données Utilisateur

Nous souhaitons utiliser l'API REST jsonplaceholder qui fournit des informations sur des utilisateurs. Nous voulons récupérer les détails d'un utilisateur spécifique (comme son nom, son email, et d'autres informations) à partir de cette API. Vous pouvez créer une fonction qui utilise fetch pour faire cet appel API, mais vous voudrez que cette fonction soit asynchrone pour pouvoir gérer l'attente de la réponse.

Étapes

  1. Créer une fonction asynchrone pour récupérer les données.
  2. Utiliser fetch pour faire une requête à l'API.
  3. Gérer les erreurs avec des promesses.
  4. Utiliser les données récupérées pour mettre à jour l'interface utilisateur ou effectuer d'autres actions.

Exemple de Code

Voici un exemple concret :

const getUserById = async (userId) => {
try {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${userId}`);

// Vérifiez si la réponse est correcte
if (!response.ok) {
throw new Error(`Erreur HTTP : ${response.status}`);
}

// Parsez la réponse JSON
const user = await response.json();
return user; // Retournez l'utilisateur
} catch (error) {
console.error('Erreur lors de la récupération de l\'utilisateur:', error);
throw error; // Relancez l'erreur pour une gestion ultérieure
}
}

// Utilisation de la fonction asynchrone
getUserById(1)
.then(user => {
console.log('Détails de l\'utilisateur:', user);
// Mettez à jour l'interface utilisateur ou effectuez d'autres actions avec les données
})
.catch(error => {
console.error('Erreur capturée lors de l\'appel à getUserById:', error);
});

getUserById(-9851)
.then(user => {
console.log('Détails de l\'utilisateur:', user);
// Mettez à jour l'interface utilisateur ou effectuez d'autres actions avec les données
});
.catch(error => {
console.error('Erreur capturée lors de l\'appel à getUserById:', error);
});

Explication

  1. Asynchrone : La fonction getUserById est déclarée avec async, ce qui permet d'utiliser await à l'intérieur. Cela rend le code plus lisible et évite les then imbriqués.

  2. Requête API : On utilise fetch pour faire une requête à une API REST. La réponse est attendue de manière asynchrone grâce à await.

  3. Gestion des Erreurs : Si la réponse n'est pas correcte (!response.ok), une erreur est lancée, ce qui permet de capturer et de gérer les erreurs de manière centralisée.

  4. Utilisation des Données : La promesse retournée par getUserById est ensuite utilisée pour manipuler les données utilisateur, par exemple, pour les afficher sur l'interface utilisateur.

Regardez le résultat dans la console

Il y a une forte probabilité que l'affichage générant une erreur (car id inconnu) s'affiche avant celui de l'utilisateur 1. Il faut bien comprendre que les deux appels sont asynchrones. La réponse de la deuxième requête peut arriver avant le retour de la première.

Si vous voulez gérer les réponses de manière synchrone, il faut utiliser Promise.all() qui permet de d'effectuer le then uniquement lorsque tous les resolve des méthodes asynchrones sont retournés.
Attention : si une des méthodes retourne un reject alors l'erreur est récupérée dans le catch et les retours des autres méthodes asynchrones sont perdus.

Promise.all([getUserById(1), getUserById(2), getUserById(3),])
.then(([user1, user2, user3]) => {
//le code ci-dessous est exécuté uniquement
//quand les 3 appels à getUserById sont
console.log('Détails de l\'utilisateur 1:', user1);
console.log('Détails de l\'utilisateur 2:', user2);
console.log('Détails de l\'utilisateur 2:', user3);
})
.catch(error => {
console.error('Une des requêtes a échoué :', error);
});
Ce qu'il faut retenir

Pourquoi utiliser une Promesse ?

  • Contrôle de Flux : En rendant la fonction asynchrone, vous vous assurez que le code qui dépend des résultats de l'API ne s'exécute qu'après que les données ont été récupérées.

  • Gestion des Erreurs : Les promesses permettent une gestion des erreurs plus propre, en centralisant la logique d'erreur dans un catch.

  • Lisibilité : Le code devient plus lisible, ce qui est essentiel lors de l'écriture de code asynchrone, surtout lorsqu'il y a plusieurs opérations qui dépendent les unes des autres.

Exercice : récupération des données d'utilisateurs et de leurs publications

Contexte

Vous devez créer une petite application qui récupère des données d'utilisateurs et leurs publications depuis une API. Pour chaque utilisateur, vous devrez récupérer les détails et, ensuite, récupérer toutes les publications de cet utilisateur. L'API que vous pouvez utiliser est JSONPlaceholder, qui fournit des exemples de données.

Étapes à suivre

  1. Récupérer les utilisateurs : Utilisez l'API https://jsonplaceholder.typicode.com/users pour récupérer une liste d'utilisateurs.
  2. Pour chaque utilisateur, récupérez leurs publications en utilisant l'API https://jsonplaceholder.typicode.com/posts?userId={userId}.
  3. Utilisez Promise.all() pour attendre que toutes les promesses de récupération de publications soient résolues avant d'afficher les résultats.
  4. Affichez le nom de l'utilisateur et le titre de chaque publication associée à cet utilisateur dans la console.

Je vous donne une partie du code pour vous guider vers le bonne solution. Je veux aussi que vous commenciez à prendre de bonnes habitudes. Par exemple, il faut prendre l'habitude d'utilser map si on souhaite transformer un tableau en un autre plutôt et un forEach si on souhaite simplement itérer sans retourner de valeur (par exemple l'afficher)


const fetchUsers = async () => {
/*À vous de jouer*/
return response.json();
};

const fetchPostsByUserId = async (userId) => {
/*À vous de jouer*/
return response.json();
};

const displayUserPosts = async () => {
try {
// 1. Récupérer des utilisateurs
const users = await fetchUsers();

// 2. Pour chaque utilisateur, récupérer ses publications
const postPromises = users.map(/*À vous de jouer*/);

// 3. Attendre que toutes les promesses soient résolues
const posts = await Promise.all(/*À vous de jouer*/);

// 4. Afficher les résultats
users.forEach((user, index) => {
/*À vous de jouer*/
});
} catch (error) {
console.error('Une erreur s\'est produite:', error);
}
};

// Appeler la fonction pour afficher les utilisateurs et leurs publications
displayUserPosts();