74
48

More than 5 years have passed since last update.

Fetch APIで取得したjsonを任意の型で取得する

Last updated at Posted at 2019-02-27

Fetch API使っていますでしょうか?便利な反面、取扱いが面倒な点もあります。

「そもそもFetch APIってなに?」って方はこちらをご参考ください。
https://qiita.com/tomoyukilabs/items/9b464c53450acc0b9574

Fetch APIの面倒な点

  • せっかくTypeScriptを使っていても、レスポンスのjsonがany型になる
  • いちいちresponse.okで出し分けをする等の処理が必要になる

そこで、fetchをそのまま使うのではなく、fetchをラップして上記の課題を解決した(レスポンスを型を付けつつ便利に安全に取り出す)ものを共通的に使い回すようにしたいというのが今回のモチベーションです。

なお、個人的にReactでSSRしているのでクライント・サーバー両方で使えるisomorphic-unfetchを使っていますが、普通のFetch APIでも同じだと思います。(たぶん。。違っていたら教えてください)

fetchをラップする

fetch(input: RequestInfo, init?: RequestInit)のreturn typeはPromise<Response>になるので、そいつをラップする関数を作ります。response.okで出し分けし、jsonを取得できた場合のみresolveとし、それ以外の全ての場合はrejectとします。こうすることで、このラップされたfetchを使用する側では、単純にthenかcatchでレスポンスの結果を振り分けることが可能になります。jsonの型は型引数(T)で指定できるようにします。

fetcher.ts
// Tはレスポンスのjsonの型を指定する
const wrap = <T>(task: Promise<Response>): Promise<T> => {
  return new Promise((resolve, reject) => {
    task
      .then(response => {
        if (response.ok) {
          response
            .json()
            .then(json => {
              // jsonが取得できた場合だけresolve
              resolve(json)
            })
            .catch(error => {
              reject(error)
            })
        } else {
          reject(response)
        }
      })
      .catch(error => {
        reject(error)
      })
  })
}

あとは、本来のfetchを上記の関数でラップするだけです。

fetcher.ts
import fetch from 'isomorphic-unfetch'

// const wrap = ...

const fetcher = <T = any>(
  input: RequestInfo,
  init?: RequestInit
): Promise<T> => {
  return wrap<T>(fetch(input, init))
}

export default fetcher

使用する側ではこうなります。例ではQiitaの記事を取得するAPIを叩いています。実際にVSCode等のエディタで動かしてみるとわかりますが、itemsの型がQiitaItemsになっていてArrayのメソッドであるforEachが使えていますし、その要素のitemの型もきちんと取得できているので、item.idやitem.titleといったコード補完も効きます。

import fetcher from './fetcher'

// レスポンスの型を定義
type QiitaItems = Array<{
  id: string
  title: string
  url: string
}>

function getQiitaItems() {
  // レスポンスの型を指定
  fetcher<QiitaItems>(
    `https://qiita.com/api/v2/authenticated_user/items?per_page=5`,
    {
      headers: {
        Authorization: `Bearer ${process.env.QIITA_ACCESS_TOKEN}`
      }
    }
  )
    // item.idやitem.title、item.urlが参照できる
    .then(items => items.forEach(item => console.log(item.id)))
    .catch(err => console.log(err))
}

リポジトリ

記事を読んでいただきありがとうございました。

最後にリポジトリを載せておきます。
https://github.com/masaki-koide/my-portfolio/blob/master/src/app/api/fetcher.ts

ここでは更に、timeoutという関数でラップしてfetch通信のタイムアウトを設定できるようにしています。仕組みとしては、setTimeoutで指定ミリ秒数後にrejectするpromiseと本来のタスクを行うpromiseをPromise.raceさせています。良ければ参考にしてみてください。

74
48
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
74
48