読者です 読者をやめる 読者になる 読者になる

tacamy.blog

JavaScriptを勉強中の人のブログです。

jQuery.ajax()の代替としてFetch APIをざっくり使ってみる

jQueryを使わずにAjaxをしたくて、とはいえ生のXHR(XMLHttpRequest )を扱うのはめんどくさいっていうときに、Fetch APIを使ってみました。そのとき調べたことの覚え書きです。

Fetch APIって?

ここに、Jxck先生のすばらしい記事があります。

jxck.hatenablog.com

正直ぜんぜん理解できてないのですが(🙇)、ものすごくざっくりいうと、

  • jQueryAjax並に簡潔に記述できる
  • Promiseベースの設計で、結果はPromiseで返される
  • XHRよりも細かな制御ができる

みたいな感じなのかなと思いました。

ちなみに、先の記事では単なるXHRの代わりじゃないと記載されてるので、FetchとかFetch APIの理解にはそちらを読んでもらった方がいいかと思います。。

ブラウザサポート状況

Can I use... Support tables for HTML5, CSS3, etcを見る限り、IESafariiOS Safari以外は対応してるみたいです。

私の場合はそれでもよかったので使ってないのですが、pollyfilもあるみたいです。

github.com

GET

次のような感じでGETができます。簡単!

fetch(url)
  .then(response => {
    return response.text();
  })
  .then(body => {
     document.body.innerHTML = body;
  });

先述したとおり、Fetch APIではPromiseが返ってくるため、then()で繋げて書いていくことができます。

レスポンスは、以下のいずれかの形式で取得できます。

  • プレーンテキスト : response.text()
  • JSON : response.json()
  • ArrayBuffer : response.arrayBuffer()
  • Blob : response.blob()

POST

POSTする場合は、fetch()の第二引数にオプションでmethod: 'POST'を指定するだけです。$.ajax()っぽい!

fetch(url, {
    method: 'POST',
    body: new FormData(document.getElementById('form'))
  })
  .then(response => {
    return response.json();
  })
  .then(json => {
    ...
  });

サーバーに値を渡すには、bodyオプションに指定できます。

クロスオリジン通信

オリジンが異なる(別ドメインの)サーバーと通信したい場合は、次のようにCORSを使います。

1. クライアント側で、fetch()modeオプションに'cors' を指定する

fetch(url, {
    mode: 'cors'
  })
  .then(response => {
    ...
  });

2. サーバー側でリクエストヘッダのOriginをチェックする

GET /api HTTP/1.1
Origin: http://xxx.com

3. サーバー側でレスポンスヘッダにAccess-Control-Allow-Originを追加する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com

これで、クロスオリジン通信が可能になります。

Access-Control-Allow-Origin: *APIの場合は1.の指定だけでできると思います。

Cookieを許可

Cookieを許可するには、次のように指定します。

1. クライアント側でfetch()credentialsオプションに'include'を指定する

fetch(url, {
    mode: 'cors',
    credentials: 'include'
  })
  .then(response => {
    ...
  });

2. サーバー側でレスポンスヘッダにAccess-Control-Allow-Credentials: trueを追加する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Credentials: true

独自のリクエストヘッダを追加

とあるAPIを利用するときに、独自のリクエストヘッダを追加して通信する必要がありました。その場合、次のように指定できます。

1. クライアント側でfetch()headersオプションに、オブジェクトの形式で独自ヘッダーを指定する

fetch(url, {
    method: 'GET',
    mode: 'cors',
    headers: {
      'X-MyRequest': 'hoge'
    }
  })
  .then(response => {
    ...
  });

2. サーバー側でプリフライトリクエストのリクエストヘッダを確認する

OPTIONS /api HTTP/1.1
Origin: http://xxx.com
Access-Control-Request-Method: GET,POST,HEAD,OPTIONS
Access-Control-Request-Headers: X-MyRequest

3. 許可する場合は、サーバー側からレスポンスヘッダを返す

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Methods: GET
Access-Control-Allow-Headers: X-MyRequest

4. 実際のリクエストがクライアントからサーバーに送られる

サーバー側からAccess-Control-Allow-MethodsAccess-Control-Allow-Headersで許可されたものしか送れないという仕組みのようです。

独自のレスポンスヘッダを読み出す

まず最初に、ダメだった方法を。

1. サーバー側から独自のレスポンスヘッダを追加したレスポンスを返す

HTTP/1.1 200 OK
X-MyResponse: hoge

2. クライアント側でresponse.headers.get()にて取得しようとするもできない

fetch(url, {
    method: 'GET',
    mode: 'cors'
  })
  .then(response => {
    console.log(response.headers.get('X-MyResponse'));
  });

この場合、セキュアではないヘッダにアクセスしようとしたと見なされてアクセスが許可されなかったようです。

なお、以下のヘッダには、問題なくアクセス可能です。

  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma

そのため、次のようにする必要があるようです。

1. サーバー側でアクセスを許可する独自ヘッダをAccess-Control-Expose-Headersで指定する

HTTP/1.1 200 OK
Access-Control-Allow-Origin: http://xxx.com
Access-Control-Allow-Methods: GET,POST,HEAD,OPTIONS
Access-Control-Allow-Headers: X-MyRequest
Access-Control-Expose-Headers: X-MyResponse

2. クライアント側でresponse.headers.get()にて取得する

fetch(url, {
    method: 'GET',
    mode: 'cors'
  })
  .then(response => {
    console.log(response.headers.get('X-MyResponse'));
  });

サーバー側のエラーの処理

fetch()では、サーバー側でエラーが起こってもレスポンスはreject()されません。つまりcatch()できないわけです。

では、どういう場合にreject()されるのかというと、ネットワークエラーの場合のみのようです。そのため、サーバーエラーの処理は、then()の中でresponse.okをチェックする必要があります。

fetch(url)
  .then(response => {
    if (!response.ok) {
      throw Error(response.statusText);
    }
    return response;
  })
  .then(response => {
    ...
  })
  .catch(err => {
    ...
  });

まとめ

jQuery.ajax()が使えるときはそっちのが安心感あるけど、個人プロジェクトで少し触ってみる程度ならよいのかも?

※実際に使ってみたのは1年以上前だし、内容が古いかもしれないです。