JavaScriptのきほん(17)「外部データを取得しよう(3)通信エラーに備えよう」

スポンサーリンク
JavaScript

前回で、async/awaitを使って外部のデータを取得し、件数を選んで表示できるようになった。
ただし、通信に失敗すると画面が「読み込み中…」のまま止まってしまう。シリーズ最終回の今回は、try/catch/finallyとresponse.okを使って、失敗したときの備えを加えよう。

1. 通信の失敗は2種類ある

外部のデータを取得するときの失敗は、大きく2つに分けられる。

  • 通信そのものができない:ネットワークの切断など。fetch()が失敗する
  • 通信はできたが、エラーが返ってくる:URLの誤りで「404」、サーバーの不具合で「500」など

注意したいのは後者である。fetch()は、404や500が返ってきても、通信自体ができていれば失敗扱いにしない。そのため、応答の状態を自分で確認する必要がある。

2. try/catch/finallyの書き方

エラーが起きるかもしれない処理は、tryの中に書く。

try {
  // 失敗するかもしれない処理
} catch (error) {
  // 失敗したときの処理
} finally {
  // 成功しても失敗しても、最後に必ず行う処理
}

tryの中で問題が起きると、残りの処理は飛ばされてcatchに移る。errorには、失敗の内容が入っている。

3. script.js(完成版)

index.htmlは前回のままでよい。script.jsを、次の完成版に置き換えよう。

const BASE_URL = 'https://jsonplaceholder.typicode.com/todos';
const list = document.getElementById('todo-list');
const message = document.getElementById('message');
const select = document.getElementById('limit-select');
const button = document.getElementById('reload-button');

async function loadTodos() {
  message.textContent = '読み込み中...';
  list.innerHTML = '';
  button.disabled = true;
  select.disabled = true;

  try {
    const response = await fetch(`${BASE_URL}?_limit=${select.value}`);
    if (!response.ok) {
      throw new Error(`HTTPエラー:${response.status}`);
    }
    const todos = await response.json();

    todos.forEach((todo) => {
      const li = document.createElement('li');
      li.textContent = (todo.completed ? '✅ ' : '⬜ ') + todo.title;
      list.appendChild(li);
    });
    message.textContent = `${todos.length}件取得した`;
  } catch (error) {
    message.textContent = `取得に失敗した(${error.message})`;
  } finally {
    button.disabled = false;
    select.disabled = false;
  }
}

select.addEventListener('change', loadTodos);
button.addEventListener('click', loadTodos);
loadTodos();

4. コードの解説

前回のコードとの違いは、取得処理をtryで囲んだことと、response.okの確認を加えたことである。

response.okは、応答が成功(200番台)のときにtrueになる。falseなら、throw new Error()でわざとエラーを発生させ、catchに処理を移している。response.statusはHTTPのステータスコードで、成功なら200など、見つからなければ404である。
ネットワークの切断のようにfetch()自体が失敗した場合も、同じcatchで受け止められる。2種類の失敗を、1か所でまとめて扱えるわけである。

catchでは、error.messageを画面に表示している。ユーザーに「何が起きたのか」が伝わるため、画面が止まったように見えることがない。

finallyには、ボタンと選択欄を有効に戻す処理を置いた。成功しても失敗しても必ず実行されるので、操作できないままになることがない。前回は、この処理が失敗時に実行されなかったことが問題だったのである。

5. わざと失敗させてみよう

エラー処理は、実際に失敗させないと動作を確認できない。次の2つを試してみよう。
(画像は筆者のChromeで試したもの)

1.script.jsのBASE_URLの「todos」を「todoss」に書き換えて、再度index.htmlをダブルクリックしてブラウザを起動しよう。

「取得に失敗した(HTTPエラー:404)」と表示される。

2.BASE_URLを元のtodosに戻して保存し、F5キーで再読み込みする。
次にF12キーで開発者ツールを開き、「ネットワーク」タブで「キャッシュを無効化」にチェックを入れ、通信状態のプルダウン(「スロットリングなし」などと表示されている)から「オフライン」を選ぶ。

開発者ツールを開いたまま、画面の「再読み込み」ボタンを押す。

ブラウザによって文言は異なるが、「取得に失敗した(Failed to fetch)」などと表示される。

どちらの場合も画面は止まらず、ボタンも選択欄も操作できる状態に戻る。
確認が済んだら、元に戻しておこう。

6. シリーズのまとめ

3回にわたって、外部データを取得して画面に表示する流れを扱ってきた。

  • fetch()でデータを取得し、response.json()で変換する(第15回)
  • async/awaitで非同期処理を順番どおりに書く(第16回)
  • try/catch/finallyとresponse.okで失敗に備える(第17回)

次のステップとしては、取得したタスクをこれまでのToDoリストに取り込み、localStorageに保存するといった発展が考えられる。ぜひ自分なりに組み合わせてみてほしい。

コメント

タイトルとURLをコピーしました