Ответ Бенджамина предлагает отличную абстракцию для решения этой проблемы, но я надеялся на менее абстрактное решение. Явный способ решить эту проблему - просто вызвать .catch
внутренние обещания и вернуть ошибку из их обратного вызова.
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));
Сделав еще один шаг вперед, вы можете написать общий обработчик catch, который будет выглядеть так:
const catchHandler = error => ({ payload: error, resolved: false });
тогда вы можете сделать
> 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 } ]
Проблема в том, что уловленные значения будут иметь другой интерфейс, чем не уловленные значения, поэтому для очистки вы можете сделать что-то вроде:
const successHandler = result => ({ payload: result, resolved: true });
Так что теперь вы можете сделать это:
> 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!' ]
Затем, чтобы сохранить его сухим, вы получите ответ Бенджамина:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
где это сейчас выглядит
> 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!' ]
Преимущества второго решения в том, что оно абстрактное и СУХОЕ. Недостатком является то, что у вас есть больше кода, и вы должны помнить, чтобы отражать все ваши обещания, чтобы сделать вещи согласованными.
Я бы охарактеризовал свое решение как явное и ПОЦЕЛУЮ, но на самом деле менее надежное. Интерфейс не гарантирует, что вы точно знаете, было ли обещание выполнено или не выполнено.
Например, у вас может быть это:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
Это не попадется a.catch
, поэтому
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Невозможно сказать, какой из них был смертельным, а какой нет. Если это важно, то вы захотите применить и интерфейс, который отслеживает, был ли он успешным или нет (что reflect
делает).
Если вы просто хотите корректно обрабатывать ошибки, то вы можете просто рассматривать ошибки как неопределенные значения:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
В моем случае мне не нужно знать об ошибке или о том, как она произошла - мне просто важно, есть ли у меня значение или нет. Я позволю функции, которая генерирует обещание, беспокоиться о регистрации конкретной ошибки.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
Таким образом, остальная часть приложения может игнорировать свою ошибку, если она хочет, и обрабатывать ее как неопределенное значение, если оно хочет.
Я хочу, чтобы мои высокоуровневые функции благополучно выходили из строя, и не беспокоился о деталях, почему их зависимости перестали работать, и я также предпочитаю, чтобы KISS СУХОЙ, когда я должен сделать этот компромисс - именно поэтому я решил не использовать reflect
.