fetchでは正確なエラーハンドリングができない? 〜 JavaScriptのFetch APIでより安全にデータを取得する方法 〜

トラストバンク開発部・フロントエンドチームの君田(きみた)です。

先日以下のような記事を目にして、とても気になったので記事にしようと思いました。

www.builder.io

自分もフロントエンドの開発時によく使用したり、見かけたりするFetch APIです。 このFetch APIのエラーハンドリングについて言及されていた記事で、書かれていた内容を実際に試してみたので書いていきます。

Fetch APIステータスコード200以外の場合でもエラーをスローしない

任意のステータスコードを返却してくれるサービスを使用して記載していきます。

httpbin.org

例えば以下のようなコードがあったとします。

const fetchSample = async () => {
  try {
    // ステータスコード404を返すリクエスト
    const response = await fetch('https://httpbin.org/status/404');
  } catch (err) {
    console.log(err, 'error')
  }
}

この場合、存在しないURLへデータを取得しに行っていますが、fetchSampleを実行してもcatch句に書かれているconsole.logが実行されません。 実際に実行するとFetch APIステータスコード200以外の場合でもエラーをスローしないことがわかります。 これを以下のように書き換えることで、エラーをスローしcatch句に書かれているconsole.logが実行されるようになります。 これは取得したデータのjsonメソッドのエラーによりcatch句の処理が実行されています。

const fetchSample = async () => {
  try {
    // ステータスコード404を返すリクエスト
    const response = await fetch('https://httpbin.org/status/404');
    const data = await response.json();
  } catch (err) {
    console.log(err, 'error')
  }
}

ステータスコードによって処理を分岐させ、適切なハンドリングができるようにしよう!

レスポンスのステータスコードごとにエラーハンドリングすることで、どのようなことが原因でエラーになっているのかをユーザーに明確に示すことができます。 以下は受け取ったレスポンスのステータスコードを判別し、適切な処理を行うサンプルコードです。

const fetchSample = async () => {
  try {
    const res = await fetch("https://httpbin.org/status/404");
    if (!res.ok) {
      switch (res.status) {
        case 400:
          throw new Error('400 error');
        case 401:
          throw new Error('401 error');
        case 404:
          throw new Error('404 error');
        case 500:
          throw new Error('500 error');
        default:
          throw new Error('something error');
      }
    }
  } catch (err) {
    console.log(err, "error");
  }
};

これでもステータスコードを判別し、各エラーごとに適切な処理を実行することができます。 ただ、可読性を上げるためにもエラーハンドリングはcatch句に記載したいところです。

そこで、Errorクラスを継承したクラスを作成し、catch句で判別できるようにします。

class ResponseError extends Error {
  constructor(message, res) {
    super(message);
    this.response = res;
  }
}

const fetchSample = async () => {
  try {
    const res = await fetch("https://httpbin.org/status/404");
    if (!res.ok) {
      throw new ResponseError("fetch did not work properly", res);
    }
  } catch (err) {
    switch (err.response.status) {
      case 400:
        // 400エラーだった時の処理
        break;
      case 401:
        // エラーだった時の処理
        break;
      case 404:
        // エラーだった時の処理
        break;
      case 500:
        // エラーだった時の処理
        break;
      default:
        // エラーだった時の処理
        break;
    }
  }
};

これでステータスコードごとのエラーハンドリングが可能になりました。 ただ、Fetch APIの登場回数は結構多く、あらゆる箇所で使用されると思います。 ですので、毎回この実装をするのは少し面倒です。 そこで、一連の処理をラップして独自の関数を作成したらかなり使い勝手の良いものになると思います。 例えば

class ResponseError extends Error {
  constructor(message, res) {
    super(message);
    this.response = res;
  }
}

export async function myFetch(...options) {
  const res = await fetch(...options)
  if (!res.ok) {
    throw new ResponseError('Bad fetch response', res)
  }
  return res
}

Fetch APIを利用するときは上記の例でいうmyFetch関数を呼び出し、レスポンスが成功した場合(ステータスが200-299)はレスポンスデータを返し、 そうでない場合はエラーと共に、レスポンスデータを返すようにします。 これを今までのtry...catch句で使用すると、

const dataFetch = async () => {
  try {
    const res = await myFetch('https://httpbin.org/status/404')
    const user = await res.json()
  } catch (error) {
    // Errorクラスを拡張したクラスを使用しているので
    // errorオブジェクトにレスポンスデータが格納されており、
    // その中のステータスコードにより適切なハンドリングをすることができるようになる
  }
}

と実装することができかなりスッキリします。

axiosを使えばさっきのラッパー関数使わなくてもステータスコードごとにエラーハンドリング可能.....

github.com

行っていることはFetch APIとほぼ同じですが、

実際に以下のコードを試して、コンソールを確認してみてください。

const fetchSample = async () => {
  try {
    const { data } = await axios.get('https://httpbin.org/status/404')
  } catch (err) {
    console.log("err", err.response);
  }
};

fetchSample();

そうすると以下のようなjsonデータが返却されていることがわかります。

{
  data: "",
  status: 404,
  statusText: "",
  headers: AxiosHeaders,
  config: Object,
  request: XMLHttpRequest,
}

元々エラーが発生した時にステータスコードのみならず、そのほかの情報も確認することができます。

さいごに

JavaScriptのデータ取得におけるエラーハンドリングについて記載しました。 Fetch APIaxiosどちらを使用しても同じ処理が実現できますが、使用する場面やプロダクトによって適切な選択をし、 それぞれで正しくエラーを処理できるようにしたいと思いました。

弊社トラストバンクでは様々な職種で絶賛採用中です! 気になった方、是非お気軽にご連絡ください!

www.wantedly.com