KDE BLOG

不器用ですから

【JavaScript基礎】Fetch APIの基礎

これまでFetch APIをなんとなくで使っていてちゃんと理解できていなかったので、改めて調べ直して使い方を理解していこうと思います。

Fetch API概要

そもそもFetch(フェッチ)とはなんなんでしょうか?

フェッチとは、取りに行く、取ってくる、持ってくる、連れてくる、来させる、呼び出す、引き出す、惹きつける、などの意味を持つ英単語。ITの分野では機器やプログラムなどが特定の場所からデータなどを読み出す動作のことを指すことが多い。
(フェッチとは - IT用語辞典 より)

ということは、AjaxなどでAPIを通じてデータを取得する動作もfetchといえそうです。

Fetch APIとは、ページの外部からリソースを取得するためのインターフェースを定義した、Webブラウザの標準APIです。Fetch APIfetch関数など、リソースを取得するためのAPIを定義しています。
([コラム] Fetch API - Promiseを活用する · JavaScriptの入門書 #jsprimer より)

Webブラウザの標準API というのがネックかと思います。MDNでは下記のように記述されています。

Fetch API を利用すると、リクエストやレスポンスといった HTTP のパイプラインを構成する要素を操作できるようになります。また fetch() メソッドを利用することで、非同期のネットワーク通信を簡単にわかりやすく記述できるようになります。
従来、このような機能は XMLHttpRequest を使用して実現されてきました。 Fetch はそれのより良い代替となるもので、サービスワーカーのような他の技術から簡単に利用することができます。 Fetch は CORS や HTTP 拡張のような HTTP に関連する概念をまとめて定義する場所でもあります。
(Fetch 概説 - Web API インターフェイス | MDN より)

つまり、XMLHttpRequest に変わる、モダンで他の技術と連携しやすい(ローレベルな)非同期通信の手段といえます。

対応ブラウザ

IEでは使えません。また Android 4.4.4iOS Safari 10.2 以下 も非対応です。
Can I use... Support tables for HTML5, CSS3, etc

そのため上記に対応させるには Pollyfill が必要です。

構文

最も基本的な構文は下記になります。

fetch(input[, init])

第一引数(input

fetchしたいリソース(下記の2つが使用可)

  1. URLを含む USVString (基本的には文字列と考えて大丈夫かも。詳細は割愛)
  2. Request オブジェクト

第二引数(init

リクエストに適用したい設定を含むオプションの(必須ではない)オブジェクト

オプション 説明
method リクエストするメソッド。 デフォルトは GET
headers リクエストに追加したいヘッダー。Headers オブジェクトか ByteString 値を持つオブジェクトリテラルで指定
body リクエストに追加したいボディ。methodGETHEAD の場合使用できない
BlobFormDataURLSearchParams などが使用可
※本文のデータ型は "Content-Type" ヘッダーと一致する必要がある
mode リクエストで使用したいモード。クロスオリジンリクエストに対して有効なレスポンスができるか、またレスポンスのプロパティが読み取り可能かどうかを判定するために使用される。
corsno-corscors-with-forced-preflightsame-origin が使用可。
※クロスオリジンリクエストを許可するには cors (デフォルト)を設定。
詳細:Request.mode - Web API インターフェイス | MDN
credentials リクエストに使用したい秘密情報。クロスオリジンリクエストの場合、ユーザーエージェントが ほかのドメインから cookie を送信すべきかどうかを示す。
omit : 決してクッキーを送信しない。※デフォルト
same-origin : リクエストURLが呼び出し元のスクリプトと同一オリジンだった場合のみ cookie を送信する
include クロスオリジンの呼び出しであっても常にクッキーを送信する。
詳細:Request.credentials - Web API インターフェイス | MDN
cache リクエストのキャッシュのモードを指定する。
default(デフォルト), no-store, reload, no-cache, force-cache, only-if-cached が使用可
referrer リクエストのリファラを文字列(USVString)で設定する。
client(デフォルト)、no-referrer任意のurl が指定可。
redirect リダイレクトが発生した場合どう処理するかについて指定する。
follow : リダイレクト先まで追従してリソースの取得を行う ※デフォルト
error : リダイレクトが発生した場合ネットワークエラーを投げる
manual : 手動でリダイレクトを制御する
以下略 詳細: GlobalFetch.fetch() - Web API インターフェイス | MDN

戻り値

解決時にリクエストのレスポンスを表す Response オブジェクトを取得できる Promise オブジェクト。

基本的なリクエスト&レスポンスの取得

それでは実際に、一番基本的な GETリクエスト を行ってみます。

fetch('https://holidays-jp.github.io/api/v1/date.json')
  .then(response => response.text())
  .then(text => {
    console.log(text);
  });

祝日一覧APIをコールして、その結果を文字列として出力しています。
処理の流れは下記になります。

  1. fetch(...) の結果が Promiseオブジェクトして返ってくる
  2. このPromiseの結果は Response オブジェクトで、 Response.text() でリクエストの結果を文字列で解決されたPromiseを得ることができる
  3. そして再び then() を使って、文字列にアクセスできる

fetch() から返されるPromiseは404でもrejectされない

このPromiseオブジェクトはレスポンスが HTTP 404500 を返してHTTPエラーステータスの場合でも reject されません
fetch() の役割はリクエストを投げてレスポンスを受け取って返すことで「エラーを示すレスポンスを返す」という役割は成功しているからです。
reject されるのはネットワークのエラーや、何かがリクエストの完了を妨げた場合のみとなっています。

ではどうすれば良いのでしょうか。
Response オブジェクトには Response.ok という「レスポンスが成功(200-299 の範囲のステータス)したか否かの真偽値が入っているプロパティがあり、これを参照することで解決できます。
下記がそのサンプルコードです。

fetch('https://holidays-jp.github.io/api/v1/date.jsonnnnnnnnnnnn') // 404 (Not Found)
  .then(response => {
    if (response.ok) {
      return response.text();
    } else {
      return Promise.reject(new Error('エラーです!'));
    }
  })
  .then(text => {
    console.log(text);
  })
  .catch(e => {
    console.log(e.message); // エラーです!
  });

Response オブジェクト

fetchを使いこなす鍵は Response オブジェクトにありますので、詳しく見ていきたいと思います。

プロパティ

プロパティ名 説明 備考
headers レスポンスに関連した Headers オブジェクト 読取専用
ok レスポンスが成功(200-299 の範囲のステータス)したか否かの真偽値 読取専用
redirected レスポンスがリダイレクトの結果であるかどうかの真偽値 読取専用
status HTTPステータスコード 読取専用
statusText ステータスコードに対応したステータスメッセージ
例)200の場合:OK 404の場合:Not Found etc
読取専用
type レスポンスのタイプ 例)basic, cors etc) 読取専用
url レスポンスのURL 読取専用
useFinalURL レスポンスの最後の URL かどうかの真偽値 ServiceWorkersのみ適用される。
他では undefined になる
body コンテンツのボディを示す ReadableStream の単純なゲッター 読取専用
bodyUsed レスポンスで body が既に使用されているかどうかの真偽値 読取専用

メソッド

メソッド名 説明 備考
arrayBuffer() ボディテキストを ArrayBuffer として解析した結果で解決されるPromise Body のミックスインとして定義されている
blob() ボディテキストを Blob として解析した結果で解決されるPromise 同上
formData() ボディテキストを FormData として解析した結果で解決されるPromise 同上
json() ボディテキストを Json として解析した結果で解決されるPromise 同上
text() ボディテキストを 文字列(厳密にはUSVString) として解析した結果で解決されるPromise 同上
以下略 詳細:Response - Web API インターフェイス | MDN

...(略) これはもちろんただの HTTP レスポンスであり、実際の JSON ではありません。 response オブジェクトから JSON を抽出するには、 json() メソッドを使用する必要があります。(Body のミックスインとして定義されていて、これは RequestResponse の両オブジェクトに実装されています。)
(Fetch 概説 - Web API インターフェイス | MDN) より

このようにResponse オブジェクトはHTTP レスポンスに関するステータスコード、ヘッダ、本文(ボディ)に関するプロパティやメソッドを持っています

Response オブジェクトが得られるタイミング

Responseオブジェクトが得られる(= fetch() の返すPromiseが解決される)のは、レスポンスヘッダが全部返ってきたとき のようです。

HTTPレスポンスは、ステータスコード、次にヘッダ、最後に本文(レスポンスボディ)という順番のため、Responseオブジェクトが得られた瞬間は、まだ本文を取得していません。
そのため、Response.text() がPromiseを返す仕様となっています。
そしてこのPromiseはレスポンスの本文が全部到着するのを待ってから解決されます。

↑の方のサンプルで利用したResponse.ok は、ステータスコードに関する情報のため本文関係なくReponseオブジェクトから直に参照できるため、本文の取得前に判定が可能だったというわけですね。

下記がResponseオブジェクトに関してのまとめとなります。

Responseオブジェクトというのは、ヘッダーまでのレスポンスで得られた情報を表すオブジェクトであるというのが1つの側面です。statusheaders などを通してこれらの情報を得ることができます。

そして、もうひとつの側面が、これから取得されるであろうレスポンス本文を表すオブジェクトという側面です(仕様書的にはこれを Body mixin と読んでいます)。
最初の例で使用した response.text() メソッドはこれに属するメソッドです。
(まだXMLHttpRequestを使ってるの? fetchのすすめ) より

まとめ

調べてみるとfetchに関する情報は多いため今回はごく基本的なところだけになりますが、Response オブジェクトを中心にfetchでできることを見てみると理解がしやすいように思えました。
レスポンスボディは ストリーム で扱える点など、まだ掴めきれていないところが多々ありますそれはまた別途学習して掴んでいこうと思います。

Fetch API には標準でタイムアウト処理やリトライ処理がないので、実際のアプリケーション開発には、axiossuperAgent といったライブラリを利用するのが良いかもしれません。

参考