フォールトトレラント Promise.all
並列に複数の URL を取得したいです。
これはそのためのコードです:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'https://api.github.com/users/jeresig'
];
Promise.all(urls.map(url => fetch(url)))
// 各レスポンスに対し、そのステータスを表示
.then(responses => { // (*)
for(let response of responses) {
alert(`${response.url}: ${response.status}`);
}
));
問題は、任意のリクエストが失敗した場合、Promise.all
はエラーで reject し、他のすべてのリクエストの結果を失うことです。
これは良くありません。
行 (*)
で配列 responses
がフェッチに成功したレスポンスオブジェクトと、失敗したエラーオブジェクトを含むようにコードを修正してください。
例えば、URL の1つが悪い場合、次のようになるべきです:
let urls = [
'https://api.github.com/users/iliakan',
'https://api.github.com/users/remy',
'http://no-such-url'
];
Promise.all(...) // URL を取得するあなたのコード...
// ...そして結果の配列のメンバとして取得エラーを渡します...
.then(responses => {
// 3 つの url => 3 つの配列要素
alert(responses[0].status); // 200
alert(responses[1].status); // 200
alert(responses[2]); // TypeError: failed to fetch (内容は異なるかもしれません)
});
P.S. このタスクでは、response.text()
や response.json()
を使った完全なレスポンスをロードする必要はありません。適切な方法で取得エラーを処理してください。
解法は実際には非常に単純です。
これを見てください:
Promise.all(
fetch('https://api.github.com/users/iliakan'),
fetch('https://api.github.com/users/remy'),
fetch('http://no-such-url')
)
ここには Promise.all
に行く fetch(...)
promise の配列があります。
Promise.all
が動作する方法を変えることはできません: もしエラーを検出した場合、それを reject します。したがって、エラーが発生しないようにする必要があります。代わりに、 fetch
エラーが発生した場合、それを “通常の” 結果として扱う必要があります。
これはその方法です:
Promise.all(
fetch('https://api.github.com/users/iliakan').catch(err => err),
fetch('https://api.github.com/users/remy').catch(err => err),
fetch('http://no-such-url').catch(err => err)
)
つまり、.catch
はすべての promise のエラーを取り、それを正常に返します。promise の動作ルールにより、.then/catch
ハンドラが値(エラーオブジェクトか他のなにかなのかは関係ありません)を返した場合、実行は “正常の” フローを続けます。
したがって、.catch
は、エラーを “正常の” 結果として外側の Promise.all
に返します。
このコード:
Promise.all(
urls.map(url => fetch(url))
)
は次のように書き直すことができます:
Promise.all(
urls.map(url => fetch(url).catch(err => err))
)