Odpowiedź Benjamina oferuje świetną abstrakcję do rozwiązania tego problemu, ale liczyłem na mniej abstrakcyjne rozwiązanie. Wyraźnym sposobem rozwiązania tego problemu jest po prostu wywołanie .catch
wewnętrznych obietnic i zwrócenie błędu z ich wywołania zwrotnego.
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));
Idąc o krok dalej, możesz napisać ogólną procedurę obsługi połowów, która wygląda następująco:
const catchHandler = error => ({ payload: error, resolved: false });
to możesz zrobić
> 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 } ]
Problem polega na tym, że przechwycone wartości będą miały inny interfejs niż nieprzechwycone wartości, więc aby to wyczyścić, możesz zrobić coś takiego:
const successHandler = result => ({ payload: result, resolved: true });
Teraz możesz to zrobić:
> 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!' ]
Następnie, aby zachować SUCHO, dochodzisz do odpowiedzi Benjamina:
const reflect = promise => promise
.then(successHandler)
.catch(catchHander)
gdzie teraz wygląda
> 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!' ]
Zaletą drugiego rozwiązania jest to, że jest ono abstrakcyjne i SUCHE. Minusem jest to, że masz więcej kodu i musisz pamiętać, aby odzwierciedlić wszystkie swoje obietnice, aby wszystko było spójne.
Scharakteryzowałbym moje rozwiązanie jako jednoznaczne i KISS, ale rzeczywiście mniej niezawodne. Interfejs nie gwarantuje, że dokładnie wiesz, czy obietnica się powiodła, czy nie.
Na przykład możesz mieć to:
const a = Promise.resolve(new Error('Not beaking, just bad'));
const b = Promise.reject(new Error('This actually didnt work'));
To nie zostanie złapane a.catch
, więc
> Promise.all([a, b].map(promise => promise.catch(e => e))
.then(results => console.log(results))
< [ Error, Error ]
Nie ma sposobu, aby stwierdzić, który był śmiertelny, a który nie. Jeśli to ważne, będziesz chciał wymusić i interfejs, który śledzi, czy się udało, czy nie (coreflect
robi).
Jeśli chcesz po prostu z wdziękiem obsługiwać błędy, możesz traktować je jako niezdefiniowane wartości:
> Promise.all([a.catch(() => undefined), b.catch(() => undefined)])
.then((results) => console.log('Known values: ', results.filter(x => typeof x !== 'undefined')))
< [ 'Resolved!' ]
W moim przypadku nie muszę znać błędu ani tego, jak się nie powiódł - dbam tylko o to, czy mam wartość, czy nie. Pozwolę, aby funkcja generująca obietnicę martwiła się rejestrowaniem konkretnego błędu.
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
W ten sposób reszta aplikacji może zignorować swój błąd, jeśli chce, i traktować go jako niezdefiniowaną wartość, jeśli chce.
Chcę, aby moje funkcje na wysokim poziomie zawiodły bezpiecznie i nie martwiłem się szczegółami, dlaczego zawiodły jego zależności, a także wolę KISS niż OSUSZANIE, gdy muszę dokonać tego kompromisu - i ostatecznie zdecydowałem się nie używać reflect
.