Відповідь Бенджаміна пропонує велику абстракцію для вирішення цього питання, але я сподівався на менш абстраговане рішення. Явний спосіб вирішити цю проблему - просто зателефонувати .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));
Зробивши цей крок далі, ви можете написати загальний обробник вилову, який виглядає приблизно так:
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!' ]
Переваги другого рішення полягають у тому, що його абстрагують і сушать. Недоліком є те, що у вас є більше коду, і ви повинні пам’ятати, щоб відобразити всі свої обіцянки, щоб зробити речі послідовними.
Я б охарактеризував своє рішення як явне та KISS, але насправді менш надійний. Інтерфейс не гарантує того, що ви точно знаєте, вдалося чи не виконано обіцянку.
Наприклад, у вас може бути таке:
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
.