La réponse de Benjamin offre une excellente abstraction pour résoudre ce problème, mais j'espérais une solution moins abstraite. La manière explicite de résoudre ce problème consiste simplement à faire appel .catch
aux promesses internes et à renvoyer l'erreur à partir de leur rappel.
let a = new Promise((res, rej) => res('Resolved!')),
b = new Promise((res, rej) => rej('Rejected!')),
c = a.catch(e => { console.log('"a" failed.'); return e; }),
d = b.catch(e => { console.log('"b" failed.'); return e; });
Promise.all([c, d])
.then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
.catch(err => console.log('Catch', err));
Promise.all([a.catch(e => e), b.catch(e => e)])
.then(result => console.log('Then', result)) // Then ["Resolved!", "Rejected!"]
.catch(err => console.log('Catch', err));
En allant plus loin, vous pouvez écrire un gestionnaire de capture générique qui ressemble à ceci:
const catchHandler = error => ({ payload: error, resolved: false });
alors tu peux faire
> Promise.all([a, b].map(promise => promise.catch(catchHandler))
.then(results => console.log(results))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!', { payload: Promise, resolved: false } ]
Le problème avec cela est que les valeurs capturées auront une interface différente de celle des valeurs non capturées, donc pour nettoyer cela, vous pourriez faire quelque chose comme:
const successHandler = result => ({ payload: result, resolved: true });
Alors maintenant, vous pouvez le faire:
> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
.then(results => console.log(results.filter(result => result.resolved))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]
Ensuite, pour le garder au sec, vous obtenez la réponse de Benjamin:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
où il ressemble maintenant
> Promise.all([a, b].map(result => result.then(successHandler).catch(catchHandler))
.then(results => console.log(results.filter(result => result.resolved))
.catch(() => console.log('Promise.all failed'))
< [ 'Resolved!' ]
Les avantages de la deuxième solution sont qu'elle est abstraite et sèche. L'inconvénient est que vous avez plus de code, et vous devez vous rappeler de refléter toutes vos promesses pour rendre les choses cohérentes.
Je caractériserais ma solution comme explicite et KISS, mais en effet moins robuste. L'interface ne garantit pas que vous savez exactement si la promesse a réussi ou échoué.
Par exemple, vous pourriez avoir ceci:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Cela ne sera pas rattrapé a.catch
, alors
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Il n'y a aucun moyen de dire lequel a été fatal et lequel ne l'a pas été. Si c'est important, vous allez vouloir appliquer et une interface qui suit si elle a réussi ou non (ce qui est le reflect
cas).
Si vous souhaitez simplement gérer les erreurs avec élégance, vous pouvez simplement traiter les erreurs comme des valeurs non définies:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
Dans mon cas, je n'ai pas besoin de connaître l'erreur ou comment elle a échoué - je me soucie simplement si j'ai la valeur ou non. Je laisse la fonction qui génère la promesse s'inquiéter de la journalisation de l'erreur spécifique.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
De cette façon, le reste de l'application peut ignorer son erreur s'il le souhaite et la traiter comme une valeur indéfinie s'il le souhaite.
Je veux que mes fonctions de haut niveau échouent en toute sécurité et ne se soucient pas des détails sur les raisons de l'échec de ses dépendances, et je préfère également KISS à DRY lorsque je dois faire ce compromis - c'est pourquoi j'ai finalement choisi de ne pas utiliser reflect
.