Aller au contenu principal

[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​

  1. 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
  2. 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
  3. 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 :

  1. Code synchrone : console.log('Warrior') → Affiche Warrior
  2. Micro task : Promise.resolve().then(a) → Exécute a(), affiche Warlock
  3. Macro task :
    • setTimeout(b, 0) s'exécute d'abord (délai 0ms)
    • Exécute b(), affiche Druid
    • Promise.resolve().then(...) dans b() est un micro task, s'exécute immédiatement, affiche Rogue
  4. Macro task : setTimeout(c, 100) s'exécute en dernier (délai 100ms), affiche Mage

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 prochain
  • setTimeout mê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 :

  1. Exécution synchrone : 1 → 4 → 7
  2. Micro task : 6
  3. Macro task (par temps de délai) : 3 → 2

Réponse​

1
4
7
6
3
2

Concepts clés​

  1. Le code dans le constructeur de Promise est exécuté de manière synchrone : console.log(4) n'est pas asynchrone
  2. Seuls .then() et .catch() sont asynchrones : Ils appartiennent aux micro tasks
  3. 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

Reference​