From 33058f2b292b3a581333bdfb21b8f671898c5060 Mon Sep 17 00:00:00 2001 From: Peter Bengtsson Date: Tue, 8 Dec 2020 14:40:17 -0500 Subject: initial commit --- files/ja/web/api/fetch_api/using_fetch/index.html | 477 ++++++++++++++++++++++ 1 file changed, 477 insertions(+) create mode 100644 files/ja/web/api/fetch_api/using_fetch/index.html (limited to 'files/ja/web/api/fetch_api/using_fetch') diff --git a/files/ja/web/api/fetch_api/using_fetch/index.html b/files/ja/web/api/fetch_api/using_fetch/index.html new file mode 100644 index 0000000000..74ff104705 --- /dev/null +++ b/files/ja/web/api/fetch_api/using_fetch/index.html @@ -0,0 +1,477 @@ +--- +title: Fetch の使用 +slug: Web/API/Fetch_API/Using_Fetch +tags: + - API + - BODY + - Experimental + - Fetch + - Guide + - HTTP + - Promise + - Response + - request +translation_of: Web/API/Fetch_API/Using_Fetch +--- +

{{DefaultAPISidebar("Fetch API")}}

+ +
+

Fetch API を利用すると、リクエストやレスポンスといった HTTP のパイプラインを構成する要素を操作できるようになります。また {{domxref("GlobalFetch.fetch","fetch()")}} メソッドを利用することで、非同期のネットワーク通信を簡単にわかりやすく記述できるようになります。

+
+ +

従来、このような機能は {{domxref("XMLHttpRequest")}} を使用して実現されてきました。 Fetch はそれのより良い代替となるもので、{{domxref("ServiceWorker_API", "サービスワーカー")}}のような他の技術から簡単に利用することができます。 Fetch は CORS や HTTP 拡張のような HTTP に関連する概念をまとめて定義する場所でもあります。

+ +

fetch の仕様は jQuery.ajax() とは主に二つの点で異なっています。

+ + + +

基本的な fetch リクエストは、本当に簡単に設定できます。以下のコードを見てください。

+ +
fetch('http://example.com/movies.json')
+  .then(response => response.json())
+  .then(data => console.log(data));
+
+ +

これはネットワーク越しに JSON ファイルを取得してコンソールに出力するスクリプトです。 fetch() の最も簡単な使い方は 1 つの引数 — fetch で取得したいリソースへのパス — のみをとり、レスポンス ({{domxref("Response")}} オブジェクト) を含む promise を返します。

+ +

これはただの HTTP レスポンスであり、実際の JSON ではありません。 response オブジェクトから JSON を抽出するには、 {{domxref("Body.json","json()")}} メソッドを使用する必要があります。({{domxref("Body")}} のミックスインとして定義されていて、これは {{domxref("Request")}} と {{domxref("Response")}} の両オブジェクトに実装されています。)

+ +
+

メモ: Body ミックスインは本文の内容を他の mime タイプとして展開する似たようなメソッドを提供しています。詳細は {{anch("Body")}} の節をご覧ください。

+
+ +

Fetch リクエストは、検索したリソースからの指示よりも Content Security Policyconnect-src ディレクティブによって制御されます。

+ +

リクエストにオプションを適用する

+ +

fetch() メソッドには 2 つ目の引数を適用することもできます。多数の設定をコントロールすることのできる init オブジェクトです。

+ +

すべての設定可能なオプションや詳細な説明を見るには {{domxref("GlobalFetch.fetch","fetch()")}} を参照してください。

+ +
// POST メソッドの実装の例
+async function postData(url = '', data = {}) {
+  // 既定のオプションには * が付いています
+  const response = await fetch(url, {
+    method: 'POST', // *GET, POST, PUT, DELETE, etc.
+    mode: 'cors', // no-cors, *cors, same-origin
+    cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached
+    credentials: 'same-origin', // include, *same-origin, omit
+    headers: {
+      'Content-Type': 'application/json'
+      // 'Content-Type': 'application/x-www-form-urlencoded',
+    },
+    redirect: 'follow', // manual, *follow, error
+    referrerPolicy: 'no-referrer', // no-referrer, *no-referrer-when-downgrade, origin, origin-when-cross-origin, same-origin, strict-origin, strict-origin-when-cross-origin, unsafe-url
+    body: JSON.stringify(data) // 本文のデータ型は "Content-Type" ヘッダーと一致する必要があります
+  })
+  return response.json(); // レスポンスの JSON を解析
+}
+
+postData('https://example.com/answer', { answer: 42 })
+  .then(data => {
+    console.log(data); // `data.json()` の呼び出しで解釈された JSON データ
+  });
+
+ +

なお、 mode: "no-cors" はリクエスト中の限られた数のヘッダーにしか許可されていません。

+ + + +

認証情報つきのリクエストの送信

+ +

ブラウザーに認証情報の入ったリクエストを送るようにするには、オリジン間の呼び出しであっても、 credentials: 'include'init オブジェクトに追加して fetch() メソッドに渡します。

+ +
fetch('https://example.com', {
+  credentials: 'include'
+});
+
+ +

リクエスト URL が呼び出しスクリプトと同一オリジンの場合だけクレデンシャルを送りたい場合、credentials: 'same-origin'を追加します。

+ +
// The calling script is on the origin 'https://example.com'
+
+fetch('https://example.com', {
+  credentials: 'same-origin'
+});
+
+ +

この代わりにブラウザーがリクエストにクレデンシャルを含んでないことを保証するには、credentials: 'omit'を使います。

+ +
fetch('https://example.com', {
+  credentials: 'omit'
+})
+ +

JSON データのアップロード

+ +

{{domxref("GlobalFetch.fetch","fetch()")}} を使って JSON-エンコードしたデータを POST します。

+ +
const data = { username: 'example' };
+
+fetch('https://example.com/profile', {
+  method: 'POST', // or 'PUT'
+  headers: {
+    'Content-Type': 'application/json',
+  },
+  body: JSON.stringify(data),
+})
+.then(response => response.json())
+.then(data => {
+  console.log('Success:', data);
+})
+.catch((error) => {
+  console.error('Error:', error);
+});
+
+ +

ファイルのアップロード

+ +

ファイルは HTML <input type="file" /> input 要素や、 {{domxref("FormData.FormData","FormData()")}} や {{domxref("WindowOrWorkerGlobalScope/fetch","fetch()")}} を使ってアップロードできます。

+ +
const formData = new FormData();
+const fileField = document.querySelector('input[type="file"]');
+
+formData.append('username', 'abc123');
+formData.append('avatar', fileField.files[0]);
+
+fetch('https://example.com/profile/avatar', {
+  method: 'PUT',
+  body: formData
+})
+.then(response => response.json())
+.then(result => {
+  console.log('Success:', result);
+})
+.catch(error => {
+  console.error('Error:', error);
+});
+
+ +

複数のファイルのアップロード

+ +

HTML の <input type="file" multiple /> 入力欄と {{domxref("FormData.FormData","FormData()")}} と {{domxref("GlobalFetch.fetch","fetch()")}} を使用してファイルをアップロードすることができます。

+ +
const formData = new FormData();
+const photos = document.querySelector('input[type="file"][multiple]');
+
+formData.append('title', 'My Vegas Vacation');
+for (let i = 0; i < photos.files.length; i++) {
+  formData.append('photos', photos.files[i]);
+}
+
+fetch('https://example.com/posts', {
+  method: 'POST',
+  body: formData,
+})
+.then(response => response.json())
+.then(result => {
+  console.log('Success:', result);
+})
+.catch(error => {
+  console.error('Error:', error);
+});
+
+ +

テキストファイルの1行ずつの処理

+ +

レスポンスから読み込まれるチャンクは、行の境界できれいに分割されておらず、文字列ではなく Uint8Arrays になっています。テキストファイルをフェッチして一行ずつ処理したい場合、これらの複雑な処理を行うのはあなた次第です。次の例は、行イテレータを作成することでこれを行う方法の一つを示しています (簡単にするため、テキストは UTF-8 であると仮定しており、フェッチエラーは処理していません)。

+ +
async function* makeTextFileLineIterator(fileURL) {
+  const utf8Decoder = new TextDecoder('utf-8');
+  const response = await fetch(fileURL);
+  const reader = response.body.getReader();
+  let { value: chunk, done: readerDone } = await reader.read();
+  chunk = chunk ? utf8Decoder.decode(chunk) : '';
+
+  const re = /\n|\r|\r\n/gm;
+  let startIndex = 0;
+  let result;
+
+  for (;;) {
+    let result = re.exec(chunk);
+    if (!result) {
+      if (readerDone) {
+        break;
+      }
+      let remainder = chunk.substr(startIndex);
+      ({ value: chunk, done: readerDone } = await reader.read());
+      chunk = remainder + (chunk ? utf8Decoder.decode(chunk) : '');
+      startIndex = re.lastIndex = 0;
+      continue;
+    }
+    yield chunk.substring(startIndex, result.index);
+    startIndex = re.lastIndex;
+  }
+  if (startIndex < chunk.length) {
+    // last line didn't end in a newline char
+    yield chunk.substr(startIndex);
+  }
+}
+
+async function run() {
+  for await (let line of makeTextFileLineIterator(urlOfFile)) {
+    processLine(line);
+  }
+}
+
+run();
+
+ +

fetch が成功したかチェックする

+ +

ネットワークエラーに遭遇すると {{domxref("GlobalFetch.fetch","fetch()")}} promise は {{jsxref("TypeError")}} を返して reject 状態になります。サーバー側の CORS が適切に設定されていない場合も同様です(アクセス権の問題ですけどね) — 一方で例えば 404 はネットワークエラーを構成しません。fetch() が成功したかどうかの明確な判定をするには、プロミスが解決されて、{{domxref("Response.ok")}} プロパティが true になっているかなどを確認します。次のようなコードになるでしょう。

+ +
fetch('flowers.jpg')
+  .then(response => {
+    if (!response.ok) {
+      throw new Error('Network response was not ok');
+    }
+    return response.blob();
+  })
+  .then(myBlob => {
+    myImage.src = URL.createObjectURL(myBlob);
+  })
+  .catch(error => {
+    console.error('There has been a problem with your fetch operation:', error);
+  });
+
+ +

独自の request オブジェクトを fetch に渡す

+ +

fetch() を呼ぶときにリクエストしたいリソースへのパスを渡す代わりに、{{domxref("Request.Request","Request()")}} コンストラクターを使用して Request オブジェクトを作成して fetch() メソッドの引数として渡すこともできます。

+ +
const myHeaders = new Headers();
+
+const myRequest = new Request('flowers.jpg', {
+  method: 'GET',
+  headers: myHeaders,
+  mode: 'cors',
+  cache: 'default',
+});
+
+fetch(myRequest)
+  .then(response => response.blob())
+  .then(myBlob => {
+    myImage.src = URL.createObjectURL(myBlob);
+  });
+
+ +

fetch() メソッドの引数と全く同じ引数を Request() に適用させることができます。また、 request オブジェクトのコピーを作成するためにすでに存在する request オブジェクトを渡すこともできます。

+ +
const anotherRequest = new Request(myRequest, myInit);
+
+ +

これは、リクエストとレスポンスの本文を一つだけ使用するのでとても有用です。必要であれば、init オプションを変化させながらリクエスト / レスポンスを再利用できるようにコピーします。コピーは body が読まれる前でなければならず、コピーの中の body を読むとオリジナルのリクエストも既読にマークされます。

+ +
+

メモ: {{domxref("Request.clone","clone()")}} メソッドを利用してコピーを生成することもできます。これには、ほかのコピーメソッドと若干異なる意味があります — 古いリクエストの body がすでに読み込まれていた場合、前者は失敗しますが、clone() は失敗しません (レスポンスでも同じです)。

+
+ +

Headers

+ +

{{domxref("Headers")}} インターフェースでは、 {{domxref("Headers.Headers","Headers()")}} コンストラクターを使用して、ヘッダーオブジェクトを作成することができます。ヘッダーオブジェクトはシンプルな複数の名前と値の Map です。

+ +
const content = 'Hello World';
+const myHeaders = new Headers();
+myHeaders.append('Content-Type', 'text/plain');
+myHeaders.append('Content-Length', content.length.toString());
+myHeaders.append('X-Custom-Header', 'ProcessThisImmediately');
+
+ +

同じことはコンストラクターに配列の配列かオブジェクトリテラルを渡すことで達成できます。

+ +
const myHeaders = new Headers({
+  'Content-Type': 'text/plain',
+  'Content-Length': content.length.toString(),
+  'X-Custom-Header': 'ProcessThisImmediately'
+});
+
+ +

ヘッダーの中身を見たり、検索することができます。

+ +
console.log(myHeaders.has('Content-Type')); // true
+console.log(myHeaders.has('Set-Cookie')); // false
+myHeaders.set('Content-Type', 'text/html');
+myHeaders.append('X-Custom-Header', 'AnotherValue');
+
+console.log(myHeaders.get('Content-Length')); // 11
+console.log(myHeaders.get('X-Custom-Header')); // ['ProcessThisImmediately', 'AnotherValue']
+
+myHeaders.delete('X-Custom-Header');
+console.log(myHeaders.get('X-Custom-Header')); // [ ]
+
+ +

いくつかの操作は {{domxref("ServiceWorker_API","ServiceWorkers")}} でしか役立ちませんが、ヘッダーを操作するためのより良い API を提供しています。

+ +

Headers のメソッドはすべて、有効な HTTP ヘッダーではない名前が渡されたとき TypeError を投げます。 immutable ガード (下記参照) がかかっている場合も、 TypeError を投げます。もしくはエラーを投げずに失敗します。例を見てください。

+ +
const myResponse = Response.error();
+try {
+  myResponse.headers.set('Origin', 'http://mybank.com');
+} catch (e) {
+  console.log("銀行のふりをしないで下さい!");
+}
+
+ +

ヘッダーの良い使用方法としては、以下のように、処理を行う前に、コンテンツタイプが正しいかどうか判定する等の使い方があります。

+ +
fetch(myRequest)
+  .then(response => {
+     const contentType = response.headers.get('content-type');
+     if (!contentType || !contentType.includes('application/json')) {
+       throw new TypeError("Oops, we haven't got JSON!");
+     }
+     return response.json();
+  })
+  .then(data => {
+      /* process your data further */
+  })
+  .catch(error => console.error(error));
+
+ +

Guard

+ +

ヘッダーは、リクエストで送信でき、レスポンスで受信できます。また、どの情報が変更できる(または、すべき)かといったさまざまな制限があります。そのため、ヘッダーは guard プロパティを持っています。これはリクエストやレスポンスに含まれませんが、ヘッダーオブジェクトでできる変更操作に影響を与えます。

+ +

設定できるガード値には以下のものがあります。

+ + + +
+

メモ: request のガードされたヘッダーの Content-Length ヘッダーは追加や変更できない可能性があります。同様に、レスポンスヘッダに Set-Cookie を挿入することはできません。ServiceWorker は、同期レスポンスを経由してクッキーを設定できません。

+
+ +

Response オブジェクト

+ +

すでに見てきたように, {{domxref("Response")}} インスタンスは、 fetch() プロミスが解決(resolve)されたときに返り値として渡されます。

+ +

下記はどんな response オブジェクトでも共通で使用できる response プロパティです。

+ + + +

Response オブジェクトは JavaScript で動的に作ることもできます。これは {{domxref("ServiceWorker_API", "ServiceWorkers")}} 内において非常に役立ちます。例えばリクエストを受け取ったときに {{domxref("FetchEvent.respondWith","respondWith()")}} メソッドによってカスタマイズされたレスポンスを返すようなときに役立ちます。

+ +
const myBody = new Blob();
+
+addEventListener('fetch', function(event) {
+  // ServiceWorker intercepting a fetch
+  event.respondWith(
+    new Response(myBody, {
+      headers: { 'Content-Type': 'text/plain' }
+    })
+  );
+});
+
+ +

{{domxref("Response.Response","Response()")}} コンストラクターはオプションとして 2 つの引数をとることができます — レスポンス本文と初期化オブジェクトです。 ({{domxref("Request.Request","Request()")}} が受け取れるものと似ています。)

+ + + +
+

メモ: 静的メソッド {{domxref("Response.error","error()")}} は単純にエラーレスポンスを返します。同様に {{domxref("Response.redirect","redirect()")}} メソッドも 指定した URL にリダイレクトするレスポンスを返します。これらはサービスワーカーにのみ関連しています。

+
+ +

Body

+ +

リクエストもレスポンスもボディを持っています。body は以下のタイプのいずれかのインスタンスです。

+ + + +

{{domxref("Body")}} ミックスインは {{domxref("Request")}} や{{domxref("Response")}} に実装されていて、コンテンツを抜き出すために以下のメソッドが定義されています。これらはすべて最終的に実際の中身で解決されるプロミスを返します。

+ + + +

これらは非テキストデータを XHR よりはるかに楽に扱うことができます。

+ +

Request 本文は、body パラメータを渡すことによって設定することができます。

+ +
const form = new FormData(document.getElementById('login-form'));
+fetch('/login', {
+  method: 'POST',
+  body: form
+});
+
+ +

Request や Response (と fetch() 関数の拡張) は自動的にコンテンツタイプを決定しようとします。Request もまた、指定されていなければ自動で Content-Type ヘッダーを設定しようとします。

+ +

使用可能かどうかの判別

+ +

Fetch API が利用できるかどうかは、{{domxref("Headers")}}、{{domxref("Request")}}、{{domxref("Response")}}、{{domxref("GlobalFetch.fetch","fetch()")}} のいずれかが {{domxref("Window")}} もしくは {{domxref("Worker")}} のスコープで参照できるかどうかによって判断できます。判断を行っている例は次のようになります。

+ +
if (window.fetch) {
+  // ここで fetch リクエストを実行
+} else {
+  // XMLHttpRequest で何か実行する?
+}
+
+ +

ポリフィル

+ +

Fetch がサポートされていないブラウザーを使うため、非サポートブラウザー用の機能を再生成する Fetch Polyfill が利用できます。

+ +

仕様書

+ + + + + + + + + + + + + + + + +
仕様書状態備考
{{SpecName('Fetch')}}{{Spec2('Fetch')}}初回定義
+ +

ブラウザーの互換性

+ + + +

{{Compat("api.WindowOrWorkerGlobalScope.fetch")}}

+ +

関連情報

+ + -- cgit v1.2.3-54-g00ecf