Eviter le « fail-fast » de Promise.All()

Avec l’arrivée d’ES6 il est maintenant possible d’exécuter en parallèle plusieurs tâches très facilement sans avoir à utiliser de composant tierce comme Q. L’avantage de cette méthode est de pouvoir optimiser les temps de traitement sur une page, attention cependant à ne pas en abuser ou vous risqueriez de compromettre votre navigateur. Cette méthode s’avère cependant intéressante si vous avez une dizaine d’actions à effectuer en même temps, mais si vous commencez à traiter un millier de demandes, je ne donne pas cher de votre navigateur qui « freezera » certainement… Mieux vaut dans ce cas chaîner les actions afin d’éviter cela, même si cela doit engendrer des traitements plus longs.

Dans le cas qui nous concerne, ES6 propose la méthode Promise.All(). Cette méthode retourne une promesse quand l’ensemble des promesses passées en argument sont résolues, ou retourne une erreur quand l’une des promesses échoue. On parle alors de fail-fast ou échec rapide. Cette dernière action peut s’avérer assez handicapante si par exemple vous êtes en train de charger des référentiels en arrière plan et que l’un d’eux ne répond pas. Vous n’avez pas forcément envie que cela arrête l’ensemble de vos traitements.

Voilà un exemple d’utilisation de Promise.All() :

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'Tâche 1');
});
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 2000, 'Tâche 2');
});
var p3 = new Promise((resolve, reject) => {
  reject('Erreur');
});
 
Promise.all([p1, p2, p3]).then(values => {
  console.log(values);
}, error => {
  console.log(error)
});
 
// Dans la console : "Erreur"

On remarque ici le rejet instantané de nos promesses au profit de l’erreur. Il existe bien entendu une manière de contourner ce comportement. Vous pouvez intercepter l’erreur de chacune de vos promesses et les gérer de cette manière :

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'Tâche 1');
});
 
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'Tâche 2');
});
 
var p3 = new Promise((resolve, reject) => {
  reject(new Error('Erreur'));
});
 
Promise.all([
  p1.catch(error => { return error }),
  p2.catch(error => { return error }),
  p3.catch(error => { return error })
]).then(values => {
  console.log(values[0]); // "Tâche 1"
  console.log(values[1]); // "Tâche 2"
  console.log(values[2]); // "Erreur"
});

Il existe heureusement une façon plus élégante d’écrire le code pour gérer le « fail-fast » :

var p1 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'Tâche 1');
});
 
var p2 = new Promise((resolve, reject) => {
  setTimeout(resolve, 1000, 'Tâche 2');
});
 
var p3 = new Promise((resolve, reject) => {
  reject(new Error('Erreur'));
});
 
Promise.all([p1, p2, p3].map(p=> {
  p.then(value => {
    console.log(value);
  }).catch(error => {
    console.log(error);
  });
}));

Ressources

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.