かもメモ

自分の落ちた落とし穴に何度も落ちる人のメモ帳

JavaScript Fetch でステータスコードを使ってエラーハンドリングしたい

Next.js の api を使って非同期処理のモックを作っていて Fetch API だとステータスコードが 200 系以外でもエラーが発生しないみたいだったのでステータスコードでエラーハンドリングできるようにしたメモ

環境
  • node.js v18.14.0
  • Next.js 13.2.4
    • React 18.2.0
  • TypeScript 5.0.2

API (Next.js)

Next.js でシンプルな API を作成

  1. POST 以外の場合は 405 (Method Not Allowed) を返す
  2. 認証が通らなかったら 400 (Bad Request) を返す
  3. 認証が通ったら 200 で {"success": "true"}json を返す

/src/api/auth.ts

import { NextApiRequest, NextApiResponse } from 'next';

type Data = {
  success: boolean;
  message?: string;
};

export default function handler(
  req: NextApiRequest,
  res: NextApiResponse<Data>,
) {
  if (req.method !== 'POST') {
    return res.status(405).send({ message: 'Bad Request!', success: false });
  }

  const { user, password } = req.body;
  if ( !login({user, password}) ) {
    return res.status(400).send({ message: 'Bad Request!', success: false });
  }

  return res.status(200).json({ success: true });
}

フロントからの Fetch

const postLoginForm = async ({ user, password }) => {
  const method = 'POST';
  const headers = {
    Accept: 'application/json',
    'Content-Type': 'application/json',
  };
  const body = JSON.stringify({ user, password });

  try {
    const res = await fetch('/api/auth', { method, headers, body });
    const { success } = await res.json();
    
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

上記のコードは API からステータスコードが 405 の時もログインに失敗して 400 が返ってきた場合も処理が cactch 節に流れません

Response.ok を使ってステータスコードが 200 番台以外は例外を発生させるようにする

Fetch API は通信そのものがエラーにならない限りステータスコードが 200番台以外でも例外を発生させないようなので、try - catch を使ったエラーハンドリングをするには意図的に例外を発生させる必要がありました

Response.ok (読取専用)
レスポンスが成功 (200–299 の範囲のステータス) したか否かを通知する論理値が入ります。
cf. Response.ok - Web API | MDN

Fetch API の返すレスポンスオブジェクトの Response.okfalse なら例外を発生させれば OK

const postLoginForm = async ({ user, password }) => {
  // 略

  try {
    const res = await fetch('/api/auth', { method, headers, body })
+     .then((res) => {
+       if ( !res.ok ) {
+         throw new Error(res.statusText);
+       }
+       return res;
+     });
    const { success } = await res.json();
    
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

レスポンスのステータスが 200 番台以外だと例外にできるようになりました
このとき error.message には Response.statusText が入っています。これは API から返されるデータの message ではなくBad Request とか Internal Server Error のようなステータスコードに対応したテキストになります。
API から返されるメッセージを error.message にしたい場合は Response から json を取り出して意図的に throw する必要があります

カスタムメッセージを使う場合

const postLoginForm = async ({ user, password }) => {
  // 略

  try {
-   const res = await fetch('/api/auth', { method, headers, body })
-     .then((res) => {
-       if ( !res.ok ) {
-         throw new Error(res.statusText);
-       }
-       return res;
-     });
-   const { success } = await res.json();
+   const data = await fetch('/api/auth', { method, headers, body })
+     .then( async(res) => {
+       const data = await res.json(); 
+       if ( !res.ok ) {
+         throw new Error(data.message);
+       }
+       return data;
+     });
+   const { success } = data;   
  
    return !!success;
  } catch (error) {
    if (error instanceof Error) {
      console.log('Error', error.message);
    } else {
      console.log('Error');
    }
    // エラー時の処理
  }

  return false;
}

ステータスコードに関わらず response.json()API から渡されたデータを取り出しその中のメッセージを threw すれば OK

所管

axios や ky を使うことが多く fetch をガッツリ使ってこなかったのでステータスコードが 200 以外でもエラーとして扱われないのは予想外だったので Response.okResponse.statusText など理解が深まってよかったです!

おわり₍ ᐢ. ̫ .ᐢ ₎


[参考]