A resposta de Benjamin oferece uma grande abstração para resolver esse problema, mas eu esperava uma solução menos abstrata. A maneira explícita de resolver esse problema é simplesmente invocar .catch
as promessas internas e retornar o erro do retorno de chamada.
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 um passo adiante, você pode escrever um manipulador de captura genérico parecido com este:
const catchHandler = error => ({ payload: error, resolved: false });
então você pode fazer
> 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 } ]
O problema é que os valores capturados terão uma interface diferente dos valores não capturados; portanto, para limpar isso, você pode fazer algo como:
const successHandler = result => ({ payload: result, resolved: true });
Então agora você pode fazer isso:
> 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!' ]
Então, para mantê-lo seco, você obtém a resposta de Benjamin:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
onde agora 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!' ]
Os benefícios da segunda solução são que ela é abstraída e SECA. A desvantagem é que você tem mais código e precisa se lembrar de refletir todas as suas promessas para tornar as coisas consistentes.
Eu caracterizaria minha solução como explícita e KISS, mas de fato menos robusta. A interface não garante que você saiba exatamente se a promessa foi bem-sucedida ou não.
Por exemplo, você pode ter isso:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Isso não será pego por a.catch
, então
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Não há como saber qual foi fatal e qual não foi. Se isso é importante, convém aplicar e fazer uma interface que rastreie se foi bem-sucedida ou não (o quereflect
faz).
Se você deseja apenas manipular os erros normalmente, pode apenas tratar os erros como valores indefinidos:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
No meu caso, não preciso saber o erro ou como ele falhou - apenas me importo se tenho o valor ou não. Vou deixar a função que gera a promessa se preocupar em registrar o erro específico.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
Dessa forma, o restante do aplicativo pode ignorar seu erro, se desejar, e tratá-lo como um valor indefinido, se desejar.
Quero que minhas funções de alto nível falhem com segurança e não me preocupo com os detalhes de por que suas dependências falharam, e também prefiro o KISS ao DRY quando precisar fazer essa troca - e foi por isso que optei por não usar reflect
.