レッスンに戻る

フォールトトレラント 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))
)

サンドボックスで解答を開く