La risposta di Benjamin offre una grande astrazione per risolvere questo problema, ma speravo in una soluzione meno astratta. Il modo esplicito di risolvere questo problema è semplicemente chiamare .catch
le promesse interne e restituire l'errore dal loro callback.
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));
Facendo un ulteriore passo avanti, potresti scrivere un gestore di cattura generico che assomigli a questo:
const catchHandler = error => ({ payload: error, resolved: false });
allora puoi farlo
> 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 } ]
Il problema con questo è che i valori catturati avranno un'interfaccia diversa rispetto ai valori non catturati, quindi per ripulire questo potresti fare qualcosa del tipo:
const successHandler = result => ({ payload: result, resolved: true });
Quindi ora puoi farlo:
> 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!' ]
Quindi per mantenerlo SECCO, si arriva alla risposta di Benjamin:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
dove ora sembra
> 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!' ]
I vantaggi della seconda soluzione sono che è astratto e SECCO. L'aspetto negativo è che hai più codice e devi ricordare di riflettere tutte le tue promesse per rendere le cose coerenti.
Definirei la mia soluzione esplicita e KISS, ma in effetti meno robusta. L'interfaccia non garantisce che tu sappia esattamente se la promessa ha avuto successo o meno.
Ad esempio potresti avere questo:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Questo non verrà catturato da a.catch
, quindi
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Non c'è modo di dire quale fosse fatale e quale no. Se questo è importante, ti consigliamo di imporre e interfacciare che traccia se ha avuto successo o meno (il che lo reflect
fa).
Se vuoi solo gestire gli errori con garbo, puoi semplicemente considerare gli errori come valori non definiti:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
Nel mio caso, non ho bisogno di sapere l'errore o come è fallito - mi importa solo se ho il valore o no. Lascerò che la funzione che genera la promessa si preoccupi della registrazione dell'errore specifico.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
In questo modo, il resto dell'applicazione può ignorare il suo errore se lo desidera e trattarlo come un valore indefinito se lo desidera.
Voglio che le mie funzioni di alto livello falliscano in modo sicuro e non si preoccupino dei dettagli sul perché le sue dipendenze hanno fallito, e preferisco anche KISS a ASCIUGARE quando devo fare quel compromesso - che alla fine ho deciso di non usare reflect
.