ベンジャミンの答えはこの問題を解決するための優れた抽象化を提供しますが、私はそれほど抽象化されていない解決策を望んでいました。この問題を解決する明示的な方法は.catch
、内部のpromiseを呼び出すだけで、コールバックからエラーを返すことです。
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!' ]
2番目のソリューションの利点は、その抽象化とDRYです。欠点は、コードが増えることと、一貫性を保つためにすべての約束を反映することを忘れないことです。
私は私のソリューションを明示的かつKISSとして特徴付けますが、実際には堅牢性は低くなります。インターフェースは、promiseが成功したか失敗したかを正確に知ることを保証しません。
たとえば、次のようになります。
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!' ]
私の場合、エラーやどのように失敗したかを知る必要はありません。値があるかどうかだけ気にします。promiseを生成する関数に、特定のエラーのログ記録について心配させます。
const apiMethod = () => fetch()
.catch(error => {
console.log(error.message);
throw error;
});
このようにして、アプリケーションの残りの部分は、必要に応じてエラーを無視し、必要に応じて未定義の値として扱うことができます。
高レベルの関数が安全に失敗し、依存関係が失敗した理由の詳細について心配しないようにしたいと思います。また、そのトレードオフを行う必要がある場合は、KISSをDRYよりも優先しますreflect
。これが、最終的にを使用しないことを選択した理由です。