Les promesses
Définition
Nous avons déjà utilisé des promesses sans le savoir lorsque nous avons manipulé fetch
.
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);
}
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 chaîner des opérations asynchrones avec des
.then()
et gérer plusieurs promesses avec des méthodes telles quePromise.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, etawait
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();