La respuesta de Benjamin ofrece una gran abstracción para resolver este problema, pero esperaba una solución menos abstracta. La forma explícita de resolver este problema es simplemente recurrir .catch
a las promesas internas y devolver el error de su devolución de llamada.
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));
Dando un paso más allá, podría escribir un controlador genérico de captura que se vea así:
const catchHandler = error => ({ payload: error, resolved: false });
entonces puedes hacer
> 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 } ]
El problema con esto es que los valores capturados tendrán una interfaz diferente a los valores no capturados, por lo que para limpiar esto puede hacer algo como:
const successHandler = result => ({ payload: result, resolved: true });
Entonces ahora puedes hacer esto:
> 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!' ]
Luego, para mantenerlo SECO, obtienes la respuesta de Benjamin:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
donde ahora parece
> 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!' ]
Los beneficios de la segunda solución son su abstracción y SECO. La desventaja es que tiene más código y debe recordar reflejar todas sus promesas para hacer que las cosas sean consistentes.
Caracterizaría mi solución como explícita y KISS, pero de hecho menos robusta. La interfaz no garantiza que sepa exactamente si la promesa tuvo éxito o no.
Por ejemplo, puede tener esto:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Esto no será atrapado a.catch
, así que
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
No hay forma de saber cuál fue fatal y cuál no. Si eso es importante, entonces querrá aplicar una interfaz que rastree si fue exitosa o no (lo que reflect
sí ocurre).
Si solo desea manejar los errores con gracia, puede tratar los errores como valores indefinidos:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
En mi caso, no necesito saber el error o cómo falló, solo me importa si tengo el valor o no. Dejaré que la función que genera la promesa se preocupe por registrar el error específico.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
De esa manera, el resto de la aplicación puede ignorar su error si lo desea, y tratarlo como un valor indefinido si lo desea.
Quiero que mis funciones de alto nivel fallen con seguridad y no me preocupe por los detalles de por qué fallaron sus dependencias, y también prefiero KISS a DRY cuando tengo que hacer ese intercambio, que es en última instancia por lo que opté por no usarlo reflect
.
allSettled
que satisface su necesidad sin que tenga que rodar la suya.Promise.all
rechazará tan pronto como cualquier promesa lo rechace, por lo que su modismo propuesto no garantiza que todas las promesas se resuelvan.