はじめに
フロントエンド開発するにおいて、APIを叩くなどfetchを行うことは多々あると思います。
今回は実案件で使ってfetchのパターンをいくつか紹介したいと思います。
実案件ではtypescriptを使っているので、今回はtypescriptベースで型にも気をつけて行きたいと思います。
実用例
普通にfetch
APIの内容は適当なんでこんなのがあるかわからないですが、Userデータをfetchで取得する場合。
interfaceを使って最初にどんなデータが来るのかの型を定義しておきます。これはtypescript特有の操作ですね。
// 型定義
interface User {
  id: number;
  name: string;
  email: string;
}
// fetch関数
const fetchData = async (url: string, headers: HeadersInit): Promise<User> => {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error('response error');
    }
    const data: User = await response.json();
    return data;
};
// URLを入れてfetchする
const url = 'xxxxx.example.com';
const headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Bearer YOUR_TOKEN_HERE'
};
// 実際に実行する
fetchData(url, headers)
    .then(data => {
        console.log('Data received:', data);
    })
    .catch(error => {
        console.error('Error fetching data:', error);
    });;
複数のfetchが終わった後に関数を実行
続いて、複数のfetchを行った後に処理を実行したい時ってありますよね。
しかし、すべてのfetchの型が同じ場合ではない場合もあります。
そういう時、fetchの関数を型のためだけに増やしたくないですよね。。。
そんな時は Generics(ジェネリックス) を使います。
Generics(ジェネリックス)とは?
Genericsとはtypescriptなど型が明確に決まっている言語に関して、汎用的に型を設定してくれる機能のことです。
「複数のfetchを行うとき」 + 「それぞれのfetchの返り値の型が決まってないもの」
に関して使うにはもってこいですね。
// 型定義
interface User {
  id: number;
  name: string;
  email: string;
}
interface Product {
  id: number;
  name: string;
  discription: string;
  price: string;
}
// Generics(ジェネリックス)<T>でfetchを実行(大文字ならアルファベットは特に意味ないらしい)
const fetchData = async <T>(url: string): Promise<T> => {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error('response error');
    }
    const data: T = await response.json();
    return data;
};
// 返り値格納用のグローバル変数の定義
let user: User;
let product: Product;
// fetch1
const getUser = async (url: string): Promise<void> => {
    user = await fetchData<User>(url);
};
// fetch2
const getProduct = async (url: string): Promise<void> => {
    product = await fetchData<Product>(url);
};
// fetchすべてのfetchを実行
const getAllEvents = async () => {
    try {
      await Promise.all([getUser('xxxxx.example.com'), getProduct('xxxxx.example2.com')]);
      console.log('処理の開始');
      // 両方正常に実行された時に処理される。
    } catch (error) {
      console.error('Error fetching data:', error);
    }
};
このようにそれぞれのfetchごとに型を定義しておくので、fetchする関数は一つですみますね。
ちなみにこの下のPromise.allは配列に入ったものを並列で処理してくれるので、一度に複数のAPIを叩くと言った場合(特に速度などを気にしない場合)はいいかもしれないですね。
Promise.all([getHoliday('xxxxx.example.com'), getEvent('xxxxx.example2.com')]);
fetchDataを呼び出す関数の後に処理を書けばそれぞれの完了のタイミングで処理を実行できます。
同時fetchさせたくない
AbortControllerというwebAPIを使います。
let abortController = null;
const url = '' // fetch先のURL
const headers = {
      'Content-Type': 'application/json',
      'Authorization': 'Bearer YOUR_TOKEN_HERE'
};
const fetchFunc = async () => {
    if (abortController !== null) abortController.abort(); // fetch中だったら中断
    abortController = new AbortController(); // abortControllerインスタンスの作成
    const signal = abortContoller.signal; // fetch時にこのsignalがいるみたい
    
    try {
      const response = await fetch(url, { ...headers, signal });
      if (!response.ok) {
        return null;
      }
      const data = await response.json();
      abortController = null; // 正常に動いたらnullに戻す。
      return data;
    } catch (error) {
      console.error(error);
    }
}
詳しいこと長くなるので書きませんが、これで一応中断できます。(詳しいのは公式へ)
if (abortController !== null) abortController.abort();の部分が中断している部分です。
AbortContollerのインタンスのsignalを渡せば、どこでもキャンセルできそう。。。
タイムアウト(AbortControllerの応用)
ほぼほぼさっきと同じで、中断する時にタイマーを使うだけ。
AbortControllerというwebAPIを使います。
const url = '' // fetch先のURL
const headers = {
  'Content-Type': 'application/json',
  'Authorization': 'Bearer YOUR_TOKEN_HERE'
};
const fetchFunc = async () => {
    const abortController = new AbortController(); // abortControllerインスタンスの作成
    const signal = abortContoller.signal; // fetch時にこのsignalがいるみたい
    const timerId = setTimeout(() => {
        abortController.abort(); //通信に5秒かかったらキャンセル
    }, 5000);
    
    try {
      const response = await fetch(url, { ...headers, signal });
      if (!response.ok) {
        return null;
      }
      const data = await response.json();
      return data;
    } catch (error) {
      console.error(error);
    } finally {
      clearTimeout(timerId); // 処理が終わったらタイマーキャンセル
    }
}
APIとか使ってたらタイムアウト処理とかやってくれてる場合もあるけど、自前実装で通信に時間がかかる場合に使うとかならアリかも、、、?(API側でタイムアウト処理はしたほうがいいとは思います。)
終わりに
今回は実案件でfetchをよく使うことがあったので、簡単によく使うパターンを紹介してみました。
Generics(ジェネリックス)はtypescriptを使う民にとっては重宝されるものだと思うのでぜひ使ってみてください。
Promise.allも並列処理してくれる点では覚えておくといいかもしれません。
