[Medium] 📄 Promise
Qu'est-ce qu'une Promise ?
Promise est une nouvelle fonctionnalité d'ES6, principalement utilisée pour résoudre le problème du callback hell et rendre le code plus facile à lire. Une Promise représente l'achèvement ou l'échec éventuel d'une opération asynchrone, ainsi que sa valeur résultante.
Une Promise a trois états :
- pending (en attente) : État initial
- fulfilled (accompli) : Opération terminée avec succès
- rejected (rejeté) : Opération échouée
Utilisation de base
Créer une Promise
const myPromise = new Promise((resolve, reject) => {
// Opération asynchrone
const success = true;
if (success) {
resolve('Succès !'); // Changer l'état de la Promise en fulfilled
} else {
reject('Échec !'); // Changer l'état de la Promise en rejected
}
});
myPromise
.then((result) => {
console.log(result); // 'Succès !'
})
.catch((error) => {
console.log(error); // 'Échec !'
});
Application pratique : Traiter les requêtes API
// Créer une fonction commune pour traiter les requêtes API
function fetchData(url) {
return fetch(url)
.then((response) => {
// Vérifier si la response est dans la plage 200 ~ 299
if (!response.ok) {
throw new Error('Network response was not ok ' + response.statusText);
}
return response.json(); // Convertir la response en JSON et retourner
})
.catch((error) => {
// Vérifier si le réseau est anormal ou si la requête a été rejetée
console.log('There has been a problem with your fetch operation:', error);
throw error; // Relancer l'erreur
});
}
fetchData('https://jsonplaceholder.typicode.com/users/1')
.then((userData) => {
console.log('User data received:', userData);
})
.catch((error) => {
console.log('Error:', error.message);
});
Méthodes de Promise
.then() / .catch() / .finally()
promise
.then((result) => {
// Traiter le cas de succès
return result;
})
.catch((error) => {
// Traiter les erreurs
console.error(error);
})
.finally(() => {
// Toujours exécuté, que ce soit un succès ou un échec
console.log('Promise terminée');
});
Promise.all()
N'est accomplie que lorsque toutes les Promise sont accomplies. Échoue dès qu'une seule échoue.
const promise1 = Promise.resolve(3);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('foo'), 100)
);
const promise3 = Promise.resolve(42);
Promise.all([promise1, promise2, promise3]).then((values) => {
console.log(values); // [3, 'foo', 42]
});
Quand l'utiliser : Lorsqu'il faut attendre que plusieurs requêtes API soient toutes terminées avant de continuer.
Promise.race()
Retourne le résultat de la première Promise terminée (succès ou échec).
const promise1 = new Promise((resolve) =>
setTimeout(() => resolve('Numéro 1'), 500)
);
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Numéro 2'), 100)
);
Promise.race([promise1, promise2]).then((value) => {
console.log(value); // 'Numéro 2' (car terminée plus rapidement)
});
Quand l'utiliser : Définir un timeout de requête, ne prendre que la réponse la plus rapide.
Promise.allSettled()
Attend que toutes les Promise soient terminées (succès ou échec) et retourne tous les résultats.
const promise1 = Promise.resolve(3);
const promise2 = Promise.reject('Erreur');
const promise3 = Promise.resolve(42);
Promise.allSettled([promise1, promise2, promise3]).then((results) => {
console.log(results);
// [
// { status: 'fulfilled', value: 3 },
// { status: 'rejected', reason: 'Erreur' },
// { status: 'fulfilled', value: 42 }
// ]
});
Quand l'utiliser : Lorsqu'il faut connaître les résultats de toutes les Promise, même si certaines échouent.
Promise.any()
Retourne la première Promise réussie. N'échoue que si toutes échouent.
const promise1 = Promise.reject('Erreur 1');
const promise2 = new Promise((resolve) =>
setTimeout(() => resolve('Succès'), 100)
);
const promise3 = Promise.reject('Erreur 2');
Promise.any([promise1, promise2, promise3]).then((value) => {
console.log(value); // 'Succès'
});
Quand l'utiliser : Plusieurs ressources de secours, il suffit qu'une seule réussisse.
Questions d'entretien
Question 1 : Chaînage de Promise et gestion des erreurs
Déterminez la sortie du code suivant :
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => 1)
.then((x) => x + 1)
.then((x) => console.log(x))
.catch((e) => console.log('This will not run'));
Analyse
Analysons le processus d'exécution étape par étape :
Promise.resolve(1) // Valeur de retour : 1
.then((x) => x + 1) // x = 1, retourne 2
.then(() => {
throw new Error('My Error'); // Lance une erreur, va au catch
})
.catch((e) => 1) // Capture l'erreur, retourne 1 (Important : ici une valeur normale est retournée)
.then((x) => x + 1) // x = 1, retourne 2
.then((x) => console.log(x)) // Affiche 2
.catch((e) => console.log('This will not run')); // Ne s'exécute pas
Réponse : 2
Concepts clés
- catch capture l'erreur et retourne une valeur normale : Quand
catch()retourne une valeur normale, la chaîne de Promise continue d'exécuter les.then()suivants - then après catch continue de s'exécuter : Car l'erreur a été traitée, la chaîne de Promise revient à l'état normal
- Le dernier catch ne s'exécute pas : Car aucune nouvelle erreur n'a été lancée
Si on veut que l'erreur continue de se propager, il faut la relancer dans le catch :
Promise.resolve(1)
.then((x) => x + 1)
.then(() => {
throw new Error('My Error');
})
.catch((e) => {
console.log('Erreur capturée');
throw e; // Relancer l'erreur
})
.then((x) => x + 1) // Ne s'exécute pas
.then((x) => console.log(x)) // Ne s'exécute pas
.catch((e) => console.log('This will run')); // S'exécute
Question 2 : Event Loop et ordre d'exécution
Cette question inclut des concepts d'Event Loop
Déterminez la sortie du code suivant :
function a() {
console.log('Warlock');
}
function b() {
console.log('Druid');
Promise.resolve().then(() => {
console.log('Rogue');
});
}
function c() {
console.log('Mage');
}
function d() {
setTimeout(c, 100);
const temp = Promise.resolve().then(a);
console.log('Warrior');
setTimeout(b, 0);
}
d();
Comprendre l'ordre d'exécution
Regardons d'abord d() :
function d() {
setTimeout(c, 100); // 4. Macro task, délai 100ms, exécuté en dernier
const temp = Promise.resolve().then(a); // 2. Micro task, exécuté après le code synchrone
console.log('Warrior'); // 1. Exécution synchrone, sortie immédiate
setTimeout(b, 0); // 3. Macro task, délai 0ms, mais reste un macro task
}
Analyse de l'ordre d'exécution :
- Code synchrone :
console.log('Warrior')→ AfficheWarrior - Micro task :
Promise.resolve().then(a)→ Exécutea(), afficheWarlock - Macro task :
setTimeout(b, 0)s'exécute d'abord (délai 0ms)- Exécute
b(), afficheDruid Promise.resolve().then(...)dansb()est un micro task, s'exécute immédiatement, afficheRogue
- Macro task :
setTimeout(c, 100)s'exécute en dernier (délai 100ms), afficheMage
Réponse
Warrior
Warlock
Druid
Rogue
Mage
Concepts clés
- Code synchrone > Micro task (Promise) > Macro task (setTimeout)
.then()de Promise appartient aux micro tasks, exécuté après la fin du macro task actuel et avant le début du prochainsetTimeoutmême avec un délai de 0 reste un macro task, exécuté après tous les micro tasks
Question 3 : Synchrone et asynchrone dans le constructeur de Promise
Déterminez la sortie du code suivant :
function printing() {
console.log(1);
setTimeout(function () {
console.log(2);
}, 1000);
setTimeout(function () {
console.log(3);
}, 0);
new Promise((resolve, reject) => {
console.log(4);
resolve(5);
}).then((foo) => {
console.log(6);
});
console.log(7);
}
printing();
// output ?
Attention au bloc Promise
La clé de cette question : Le code dans le constructeur de Promise est exécuté de manière synchrone, seuls .then() et .catch() sont asynchrones.
Analyse de l'ordre d'exécution :
console.log(1); // 1. Synchrone, affiche 1
setTimeout(() => console.log(2), 1000); // 5. Macro task, délai 1000ms
setTimeout(() => console.log(3), 0); // 4. Macro task, délai 0ms
new Promise((resolve, reject) => {
console.log(4); // 2. Synchrone ! Dans le constructeur de Promise c'est synchrone, affiche 4
resolve(5);
}).then((foo) => {
console.log(6); // 3. Micro task, affiche 6
});
console.log(7); // 3. Synchrone, affiche 7
Flux d'exécution :
- Exécution synchrone : 1 → 4 → 7
- Micro task : 6
- Macro task (par temps de délai) : 3 → 2
Réponse
1
4
7
6
3
2
Concepts clés
- Le code dans le constructeur de Promise est exécuté de manière synchrone :
console.log(4)n'est pas asynchrone - Seuls
.then()et.catch()sont asynchrones : Ils appartiennent aux micro tasks - Ordre d'exécution : Code synchrone → micro task → macro task
Pièges courants
1. Oublier return
Oublier return dans une chaîne de Promise fait que le .then() suivant reçoit undefined :
// ❌ Incorrect
fetchUser()
.then((user) => {
fetchPosts(user.id); // return oublié
})
.then((posts) => {
console.log(posts); // undefined
});
// ✅ Correct
fetchUser()
.then((user) => {
return fetchPosts(user.id); // Ne pas oublier return
})
.then((posts) => {
console.log(posts); // Données correctes
});
2. Oublier catch pour les erreurs
Les erreurs Promise non capturées provoquent UnhandledPromiseRejection :
// ❌ Peut causer des erreurs non capturées
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
});
// ✅ Ajouter catch
fetchData()
.then((data) => {
return processData(data);
})
.then((result) => {
console.log(result);
})
.catch((error) => {
console.error('Une erreur est survenue :', error);
});
3. Abus du constructeur de Promise
Il n'est pas nécessaire d'encapsuler dans un Promise des fonctions qui retournent déjà un Promise :
// ❌ Encapsulation inutile
function fetchData() {
return new Promise((resolve, reject) => {
fetch(url)
.then((response) => resolve(response))
.catch((error) => reject(error));
});
}
// ✅ Retourner directement
function fetchData() {
return fetch(url);
}
4. Enchaîner plusieurs catch
Chaque catch() ne capture que les erreurs qui le précèdent :
Promise.resolve()
.then(() => {
throw new Error('Error 1');
})
.catch((e) => {
console.log('Capturé :', e.message); // Capturé : Error 1
})
.then(() => {
throw new Error('Error 2');
})
.catch((e) => {
console.log('Capturé :', e.message); // Capturé : Error 2
});
Sujets connexes
- async/await - Sucre syntaxique plus élégant pour les Promise
- Event Loop - Compréhension approfondie du mécanisme asynchrone de JavaScript