web-dev-qa-db-fra.com

Gestion des erreurs dans Promise.all

J'ai un tableau de promesses que je résous avec Promise.all (arrayOfPromises);

Je continue pour continuer la chaîne de promesse. Ressemble à quelque chose comme ça

existingPromiseChain = existingPromiseChain.then(function() {
  var arrayOfPromises = state.routes.map(function(route){
    return route.handler.promiseHandler();
  });
  return Promise.all(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
  // do stuff with my array of resolved promises, eventually ending with a res.send();
});

Je souhaite ajouter une instruction catch pour gérer une promesse individuelle en cas d'erreur, mais lorsque j'essaie, Promise.all renvoie la première erreur trouvée (sans tenir compte du reste), puis je ne peux pas obtenir les données du reste de les promesses dans le tableau (qui n'a pas d'erreur).

J'ai essayé de faire quelque chose comme ..

existingPromiseChain = existingPromiseChain.then(function() {
      var arrayOfPromises = state.routes.map(function(route){
        return route.handler.promiseHandler()
          .then(function(data) {
             return data;
          })
          .catch(function(err) {
             return err
          });
      });
      return Promise.all(arrayOfPromises)
    });

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
      // do stuff with my array of resolved promises, eventually ending with a res.send();
});

Mais cela ne résout pas.

Merci!

-

Modifier:

Ce que les réponses ci-dessous ont dit étaient complètement vraies, le code était cassé pour d'autres raisons. Au cas où quelqu'un serait intéressé, voici la solution à laquelle j'ai abouti ...

Node Express Server Chain

serverSidePromiseChain
    .then(function(AppRouter) {
        var arrayOfPromises = state.routes.map(function(route) {
            return route.async();
        });
        Promise.all(arrayOfPromises)
            .catch(function(err) {
                // log that I have an error, return the entire array;
                console.log('A promise failed to resolve', err);
                return arrayOfPromises;
            })
            .then(function(arrayOfPromises) {
                // full array of resolved promises;
            })
    };

Appel API (appel route.async)

return async()
    .then(function(result) {
        // dispatch a success
        return result;
    })
    .catch(function(err) {
        // dispatch a failure and throw error
        throw err;
    });

Mettre le .catch for Promise.all avant le .then semble avoir eu pour but de détecter les erreurs des promesses d'origine, mais de renvoyer l'ensemble du tableau au suivant.

Merci! 

144
Jon

Promise.all est tout ou rien. Il résout une fois que toutes les promesses du tableau ont été résolues ou rejetées dès que un d'entre elles les a rejetées. En d'autres termes, il résout avec un tableau de toutes les valeurs résolues ou rejette avec une seule erreur.

Certaines bibliothèques ont quelque chose appelé Promise.when, qui, je crois, attendrait plutôt que toutes promesses dans le tableau soit résolu ou rejeté, mais je ne le connais pas bien, et ce n'est pas dans ES6.

Votre code

Je suis d'accord avec les autres ici que votre solution devrait fonctionner. La résolution doit être effectuée avec un tableau pouvant contenir une combinaison d'objets de valeurs et d'erreurs ayant abouti. Il est inhabituel de transmettre des objets d'erreur dans le chemin de réussite, mais en supposant que votre code les attend, je ne vois aucun problème avec cela.

La seule raison pour laquelle je peux comprendre pourquoi cela ne "résoudrait" pas, c'est que le code que vous ne nous montrez pas a échoué et que vous ne voyez aucun message d'erreur à ce sujet, c'est parce que cette chaîne de promesses ne se termine pas par une dernière attraper (autant que ce que vous nous montrez quand même).

J'ai pris la liberté de factoriser la "chaîne existante" de votre exemple et de terminer la chaîne par un crochet. Cela peut ne pas vous convenir, mais pour ceux qui le liront, il est important de toujours renvoyer ou de terminer les chaînes, sinon les erreurs potentielles, même les erreurs de codage, resteront cachées (ce qui, je suppose, est arrivé ici):

Promise.all(state.routes.map(function(route) {
  return route.handler.promiseHandler().catch(function(err) {
    return err;
  });
}))
.then(function(arrayOfValuesOrErrors) {
  // handling of my array containing values and/or errors. 
})
.catch(function(err) {
  console.log(err.message); // some coding error in handling happened
});
120
jib

NOUVELLE REPONSE

const results = await Promise.all(promises.map(p => p.catch(e => e)));
const validResults = results.filter(result => !(result instanceof Error));

ANCIENNE REPONSE

Nous devons écrire Promise.all () . Ici, la solution que j'utilise dans mon projet. L'erreur sera renvoyée comme résultat normal . Une fois toutes les promesses terminées, nous pouvons filtrer l'erreur.

const Promise_all = promises => {
  return new Promise((resolve, reject) => {
    const results = [];
    let count = 0;
    promises.forEach((promise, idx) => {
      promise
        .catch(err => {
          return err;
        })
        .then(valueOrError => {
          results[idx] = valueOrError;
          count += 1;
          if (count === promises.length) resolve(results);
        });
    });
  });
};

const results = await Promise_all(promises)
const validResults = results.filter(result => !(result instanceof Error));
23
Solominh

Pour continuer la boucle Promise.all (même lorsqu'une promesse est rejetée), j'ai écrit une fonction utilitaire appelée executeAllPromises. Cette fonction utilitaire renvoie un objet avec results et errors.

L'idée est que toutes les promesses que vous passez à executeAllPromises seront intégrées dans une nouvelle promesse qui sera toujours résolue. La nouvelle promesse résout avec un tableau qui a 2 taches. Le premier spot contient la valeur de résolution (le cas échéant) et le second conserve l'erreur (si la promesse emballée est rejetée). 

Enfin, executeAllPromises accumule toutes les valeurs des promesses enveloppées et renvoie l'objet final avec un tableau pour results et un tableau pour errors.

Voici le code:

function executeAllPromises(promises) {
  // Wrap all Promises in a Promise that will always "resolve"
  var resolvingPromises = promises.map(function(promise) {
    return new Promise(function(resolve) {
      var payload = new Array(2);
      promise.then(function(result) {
          payload[0] = result;
        })
        .catch(function(error) {
          payload[1] = error;
        })
        .then(function() {
          /* 
           * The wrapped Promise returns an array:
           * The first position in the array holds the result (if any)
           * The second position in the array holds the error (if any)
           */
          resolve(payload);
        });
    });
  });

  var errors = [];
  var results = [];

  // Execute all wrapped Promises
  return Promise.all(resolvingPromises)
    .then(function(items) {
      items.forEach(function(payload) {
        if (payload[1]) {
          errors.Push(payload[1]);
        } else {
          results.Push(payload[0]);
        }
      });

      return {
        errors: errors,
        results: results
      };
    });
}

var myPromises = [
  Promise.resolve(1),
  Promise.resolve(2),
  Promise.reject(new Error('3')),
  Promise.resolve(4),
  Promise.reject(new Error('5'))
];

executeAllPromises(myPromises).then(function(items) {
  // Result
  var errors = items.errors.map(function(error) {
    return error.message
  }).join(',');
  var results = items.results.join(',');
  
  console.log(`Executed all ${myPromises.length} Promises:`);
  console.log(`— ${items.results.length} Promises were successful: ${results}`);
  console.log(`— ${items.errors.length} Promises failed: ${errors}`);
});

15
Benny Neugebauer

Utiliser Async wait -

ici, une fonction asynchrone func1 renvoie une valeur résolue et func2 génère une erreur et renvoie une valeur null dans cette situation. Nous pouvons la gérer comme nous le souhaitons et la retourner en conséquence.

const callingFunction  = async () => {
    const manyPromises = await Promise.all([func1(), func2()]);
    console.log(manyPromises);
}


const func1 = async () => {
    return 'func1'
}

const func2 = async () => {
    try {
        let x;
        if (!x) throw "x value not present"
    } catch(err) {
       return null
    }
}

callingFunction();

La sortie est - ['func1', null]

6
Nayan Patel

si vous utilisez la bibliothèque q https://github.com/kriskowal/q elle dispose de la méthode q.allSettled () qui peut résoudre ce problèmevous pouvez gérer chaque indiquer si le dossier est complet ou s'il est rejetéso 

existingPromiseChain = existingPromiseChain.then(function() {
var arrayOfPromises = state.routes.map(function(route){
  return route.handler.promiseHandler();
});
return q.allSettled(arrayOfPromises)
});

existingPromiseChain = existingPromiseChain.then(function(arrayResolved) {
//so here you have all your promises the fulfilled and the rejected ones
// you can check the state of each promise
arrayResolved.forEach(function(item){
   if(item.state === 'fulfilled'){ // 'rejected' for rejected promises
     //do somthing
   } else {
     // do something else
   }
})
// do stuff with my array of resolved promises, eventually ending with a res.send();
});
6
Mohamed Mahmoud

Pour ceux qui utilisent ES8 et qui trébuchent ici, vous pouvez faire quelque chose comme ce qui suit, en utilisant fonctions asynchrones :

var arrayOfPromises = state.routes.map(async function(route){
  try {
    return await route.handler.promiseHandler();
  } catch(e) {
    // Do something to handle the error.
    // Errored promises will return whatever you return here (undefined if you don't return anything).
  }
});

var resolvedPromises = await Promise.all(arrayOfPromises);
2
Tyler Yasaka

Nous pouvons gérer le rejet au niveau des promesses individuelles. Ainsi, lorsque nous obtenons les résultats dans notre tableau de résultats, l'index de tableau qui a été rejeté sera undefined. Nous pouvons gérer cette situation si nécessaire et utiliser les résultats restants.

Ici, j’ai rejeté la première promesse, elle est donc indéfinie, mais nous pouvons utiliser le résultat de la deuxième promesse, qui est à l’indice 1.

const manyPromises = Promise.all([func1(), func2()]).then(result => {
    console.log(result[0]);  // undefined
    console.log(result[1]);  // func2
});

function func1() {
    return new Promise( (res, rej) => rej('func1')).catch(err => {
        console.log('error handled', err);
    });
}

function func2() {
    return new Promise( (res, rej) => setTimeout(() => res('func2'), 500) );
}

2
Nayan Patel

Avez-vous envisagé Promise.prototype.finally()?

Il semble être conçu pour faire exactement ce que vous voulez - exécuter une fonction une fois que toutes les promesses sont réglées (résolues/rejetées), quelles que soient les promesses rejetées.

Depuis la documentation MDN :

La méthode finally() peut être utile si vous souhaitez effectuer un traitement ou un nettoyage une fois la promesse réglée, quel que soit son résultat.

La méthode finally() est très similaire à l'appel de .then(onFinally, onFinally) mais il existe quelques différences:

Lors de la création d'une fonction en ligne, vous pouvez la transmettre une fois au lieu d'être obligée de la déclarer deux fois ou de créer une variable pour celle-ci.

Un rappel final ne recevra aucun argument, car il n'y a aucun moyen fiable de déterminer si la promesse a été remplie ou rejetée. Ce cas d'utilisation concerne précisément le moment où vous ne vous souciez pas du motif de rejet, ou de la valeur d'exécution, et il n'est donc pas nécessaire de le fournir.

Contrairement à Promise.resolve(2).then(() => {}, () => {}) (qui sera résolu avec undefined), Promise.resolve(2).finally(() => {}) sera résolu avec 2 ..__ De même, contrairement à Promise.reject(3).then(() => {}, () => {}) (qui sera rempli avec undefined), Promise.reject(3).finally(() => {}) sera rejeté avec 3.

== repli ==

Si votre version de JavaScript ne prend pas en charge Promise.prototype.finally(), vous pouvez utiliser cette solution de contournement de Jake Archibald : Promise.all(promises.map(p => p.catch(() => undefined)));.

1
Tom Auger

Comme @jib a dit,

Promise.all est tout ou rien.

Cependant, vous pouvez contrôler certaines promesses qui sont "autorisées" à échouer et nous aimerions procéder à .then.

Par exemple.

  Promise.all([
    doMustAsyncTask1,
    doMustAsyncTask2,
    doOptionalAsyncTask
    .catch(err => {
      if( /* err non-critical */) {
        return
      }
      // if critical then fail
      throw err
    })
  ])
  .then(([ mustRes1, mustRes2, optionalRes ]) => {
    // proceed to work with results
  })
0
Herman

Vous pouvez toujours emballer votre promesse en renvoyant des fonctions de manière à détecter l'échec et à renvoyer à la place une valeur convenue (par exemple, error.message). Ainsi, l'exception ne sera pas renvoyée à la fonction Promise.all et ne la désactivera pas.

async function resetCache(ip) {

    try {

        const response = await axios.get(`http://${ip}/resetcache`);
        return response;

    }catch (e) {

        return {status: 'failure', reason: 'e.message'};
    }

}
0
Tamir Nakar

Alternativement, si vous avez un cas où vous ne vous souciez pas particulièrement des valeurs des promesses résolues quand il y a un échec mais que vous voulez toujours qu'elles soient exécutées, vous pouvez faire quelque chose comme ceci qui résoudra avec les promesses comme d'habitude quand ils réussissent tous et rejettent les promesses manquées quand aucun d'entre eux échoue:

function promiseNoReallyAll (promises) {
  return new Promise(
    async (resolve, reject) => {
      const failedPromises = []

      const successfulPromises = await Promise.all(
        promises.map(
          promise => promise.catch(error => {
            failedPromises.Push(error)
          })
        )
      )

      if (failedPromises.length) {
        reject(failedPromises)
      } else {
        resolve(successfulPromises)
      }
    }
  )
}
0
Eric