Fetch API fetch() の使い方

Fetch API の基本的な使い方や仕組みについての解説です。取得したレスポンスを DOM として扱う方法や WordPress REST API や OpenWeather API を使った簡単なサンプルなども掲載しています。

作成日:2022年5月21日

関連ページ:

参考サイト

Fetch API の概要

Fetch API は、JavaScript を使ってサーバーに HTTP リクエストを送信(AJAX を実装)できるインターフェースで、指定した URL からリソース(データ)を取得することができます。

XMLHttpRequest と同様の機能を持ちますが、よりシンプルで柔軟な API で簡潔に記述することができます。IE や一部のブラウザは対応していませんが、主要なモダンブラウザで利用できます(caniuse)。

リソース(データ)を取得するためのリクエストの送信は、fetch() メソッドを使います。

fetch() メソッドにリソースの URL を指定して呼び出すとリクエストが発行され、Response オブジェクトを結果に持つ Promise オブジェクトが返されます。

Promise は非同期処理を扱うための仕組みで、Promise オブジェクトの then() メソッドに処理(コールバック関数)を記述することができます。

また、Promise を効率よく記述するための async と await キーワードを使うこともできます。

Promise と async/await の簡単な説明は最後の方のセクションにあります。

詳細は Promise / Async Function の使い方(非同期処理) を御覧ください。

以下は、{JSON} Placeholder というテスト用の JSON データを返してくれる API にアクセスする例です。

fetch() メソッドの第1引数には取得するリソースの URL を指定します。第2引数(省略可能)には HTTP リクエストのメソッド(デフォルトは GET)やヘッダー情報などを指定することもできます。

fetch() メソッドは Promise を返すので、Promise オブジェクトの then() メソッドに必要な処理を記述して実行することができます。

リクエストが完了するとリソースが利用可能になり、その時点で、Promise は Response オブジェクトに resolve(解決)されます(結果の値として Response オブジェクトが返されます)。

//fetch() は Promise を返す(返り値を変数に代入する場合)
const promise = fetch('https://jsonplaceholder.typicode.com/users');

//fetch() のレスポンス(リクエストの結果)を then() メソッドで処理
promise.then((response) => {
  return response.json();
})
.then((data) => {
  console.log(data);
});

通常は以下のように fetch() に then() をチェーンして記述することが多いかと思います。

fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => {
  return response.json();
})
.then((data) => {
  console.log(data);
});

上記の場合、fetch() メソッドで取得した結果(Response オブジェクト)は response として2行目の then() メソッドのコールバック関数の引数に渡されます。

then() メソッドでは、取得した結果(response)を Response オブジェクトの json() メソッドを使って解析して、その結果(JSON オブジェクト)を return することで次の then() メソッドに渡しています。

次の then() メソッドでは結果の値を data として受け取ってコンソールに出力しています。

アロー関数では引数が1つの場合は引数のカッコは省略でき、処理が1行のみの場合はブロック { } と return も省略できるので上記の例は以下のように記述することもできます。

fetch('https://jsonplaceholder.typicode.com/users')
.then( response => response.json() )
.then( data  => console.log(data) );

fuction 文を使って記述すると以下のようになります。

fetch('https://jsonplaceholder.typicode.com/users')
.then( function (response) {
  return response.json();
})
.then( function(data) {
  console.log(data);
});

then() メソッドの代わりに async と await を使って記述することもできます。

以下の3行目は、fetch() に await を指定して、Promise が Response オブジェクトに resolve(解決)されるのを待って、その結果の値を変数 response に代入しています。await を指定しているので、その次の行はこの処理が完了するまで実行されません。

4行目も同様に、response.json() が完了すると変数 data に解析結果が代入され、その後、次の行の console.log() が実行されます。

//Async Function として定義
const myAsync = async (url) => {
  const response = await fetch(url); //await で fetch() が完了するまで待つ
  const data = await response.json(); //await で response.json() が完了するまで待つ
  console.log(data);
}
myAsync('https://jsonplaceholder.typicode.com/users');

即時関数(そのまま処理を実行する無名関数)使って以下のように記述することもできます。

(async () => {
  const response = await fetch('https://jsonplaceholder.typicode.com/users');
  const data =  await response.json();
  console.log(data);
})()

上記の場合、URL に指定した API は JSON 形式のダミーのユーザーデータを10件返してくれるので、コンソールを確認すると以下のような架空のユーザーデータが表示されます。

この例では公開されている REST API から取得したデータを単にコンソールに出力していますが、サーバー上のファイルからデータを取得して加工したり、フォームを送信することなどもできます。

XMLHttpRequest の場合

上記の例を XMLHttpRequest を使って書き換えると以下のようになり、Fetch API の方が簡潔に記述できるのがわかります。関連ページ:AJAX XMLHttpRequest の基本的な使い方

const xhr = new XMLHttpRequest();
xhr.responseType = 'json';
xhr.addEventListener('readystatechange', () => {
  if (xhr.readyState === 4) {
    const json = xhr.response;
    console.log(json);
  }
});
xhr.open('GET', 'https://jsonplaceholder.typicode.com/users', true);
xhr.send();

CORS(Cross-Origin Resource Sharing)

セキュリティ上の理由から、ブラウザーはスクリプトによる HTTP リクエストを制限しています。

Fetch API や XMLHttpRequest は同一オリジンポリシー (same-origin policy) に従います。そのため、公開されている API などを除き、Fetch API では同一オリジンに対してのみリソースのリクエストを行うことができ、それ以外のオリジンからの場合は正しい CORS ヘッダーを含んでいる必要があります。

詳細:MDN オリジン間リソース共有 (CORS)

例えば、以下は https://www.webdesignleaves.com/ から自身(同一オリジン)にリクエストを送信しているのでエラーにはなりません。

(async () => {
  try {
    //同一オリジンにリクエスト
    const response = await fetch('https://www.webdesignleaves.com');
    console.log('Content-Type: ' + response.headers.get('Content-Type'));
    //Content-Type: text/html; charset=UTF-8 と出力される
  } catch(e) {
    console.log('Error Message: ' + e.message);
  }
})();

但し、fetch() の引数(取得先 URL)を異なるオリジン、例えば https://example.com/ に変更して実行すると、以下のような CORS policy によるエラーになります。

エラーの内容は「https://www.webdesignleaves.com から https://example.com/ にフェッチするためのアクセスは CORS ポリシーによってブロックされています。Access-Control-Allow-Origin ヘッダーがリクエストされたリソースに存在しません」というような意味になります。

同じサーバー上でも www のある・なしでは異なるオリジンと判断されます。また、http と https では異なる HTTP プロトコルなので、例えば、Mixed Content エラーになります。

fetch() メソッド

fetch() メソッドは非同期通信で HTTP リクエストを発行し、指定された URL のリソースを取得します。

以下が fetch() メソッドの構文です。

var promise = fetch(resource, [options])

fetch() メソッドを呼び出すと、HTTP リクエストが発行され Promise オブジェクトが返されます。

引数

fetch() メソッドは以下の引数を受け取ります。第2引数の init は省略可能です。

resource
取得するリソースを以下のいずれかで指定します。
  • 取得したいリソースの URL やパス(文字列)
  • Request オブジェクト
options
リクエストの詳細(オプション)を指定するためのオブジェクトを指定することができます。
options(一部抜粋)
オプション 説明 デフォルト
method リクエストするメソッド、GET、POST など。Request.method GET
headers リクエストに追加したいヘッダー。Headers オブジェクト または 'Content-Type': 'image/jpeg' のようなオブジェクトリテラルで指定。 Request.headers(関連:HTTP ヘッダー
body リクエストに追加したい本文(リクエストボディ)。method が GET や HEAD の場合使用できません。Request.body
mode リクエストで使いたいモード。例:cors、no-cors、cors-with-forced-preflight、same-origin。 Request.mode cors
credentials request に使用したい認証情報。例:omit, same-origin, include。 Request.credentials same-origin
cache 使用したいリクエストのキャッシュのモード(default、no-store、reload、no-cache、force-cache、only-if-cached)。 Request.cache default
redirect リダイレクトをどう処理するかについての値。 Request.redirect
  • follow :自動でリダイレクトに従う
  • error :リダイレクトが起こった場合エラーを投げて中止する
  • manual :手動でリダイレクトを処理する
follow
referrer リクエストのリファラーを指定。例: about:client、 no-referrer、URL。 Request.referrer about:client

以下は resource(第1引数)にファイルのパス(この場合、同一ディレクトリにあるのでファイル名のみ)を、第2引数の options オブジェクトの method に GET を指定して、fetch() を実行する例です。

const promise = fetch('file.txt', {method: 'GET'});

リクエストメソッドのデフォルトは GET なので、上記の場合、以下のように第2引数を全部省略しても同じことになります。

const promise = fetch('file.txt');  

以下は resource(第1引数)に Request オブジェクトを指定する例です。Request オブジェクトは Request() コンストラクターを使って生成しています。内容的には上記の例と同じことです。

fetch() メソッドと Request() コンストラクターの引数は同じものです。

//Request() コンストラクタを使って Request オブジェクトを生成
const request = new Request('file.txt', {method: 'GET'});
//Request オブジェクトを指定
const promise = fetch(request);

以下は Request オブジェクトのプロパティを列挙する例です。

//デフォルトの Request オブジェクト(options の指定なし)を生成
const request = new Request('file.txt');

//Request オブジェクトのプロパティを列挙
for(let name in request) {
  //function 以外
  if(typeof request[name] !== 'function') {
    console.log(name + ': ' + request[name]);
  }
}

//以下は出力例です
method: GET
url: http://webdesignleaves.localhost/path/to/file.txt
headers: [object Headers]
destination:
referrer: about:client
referrerPolicy:
mode: cors
credentials: same-origin
cache: default
redirect: follow
integrity:
keepalive: false
signal: [object AbortSignal]
isHistoryNavigation: false
bodyUsed: false

返り値

fetch() メソッドは Response オブジェクトで resolve される Promise を返します。

以下は同じディレクトリにあるテキストファイルに対して fetch() メソッドを実行して返り値をコンソールに出力する例です(取得する file.txt と以下を記述したファイルは同一サーバー上にある前提です)。

//返り値の Promise をコンソールに出力
const promise = fetch('file.txt');
console.log(promise);

以下はコンソールの出力例です。

fetch() メソッドがリクエストを送信して、リクエストが完了するとリソースが利用可能になります。その時点で、Promise は Response オブジェクトに resolve(解決)されます。つまり、fetch() が成功すると Response オブジェクトが返されます。

但し、fetch() メソッドを実行しただけでは、Response オブジェクトに解決する Promise のインスタンスが返されるだけです。

Promise の then() メソッドを使って Response オブジェクトに resolve(解決)された値を処理します。

以下は then() メソッドで Response オブジェクトをコンソールに出力する例です。

fetch('file.txt')
//then() メソッドに登録した第1引数のコールバック関数に Response オブジェクトが渡される
.then(response => {
  //response(Response オブジェクト)をコンソールに出力(第1引数のコールバック関数の処理)
  console.log(response);
});

レスポンスの取得に成功すると then() メソッドに登録したコールバック関数の console.log() に response(Response オブジェクト)が渡されて実行されます。

以下はコンソールの出力例です。Response オブジェクトが取得されて出力されています。

Response オブジェクトはデータ自体ではなく、HTTP レスポンス全体を表すオブジェクトで、プロパティやメソッドを持っています。

この取得した Response オブジェクトのメソッドやプロパティや使って、データ本体やステータスコード、ヘッダの情報などを取得することができます。

レスポンスの取得

fetch() を実行してリクエストが成功すると、fetch() から返される Promise は Response オブジェクトで resolve します。言い換えると、リクエストが成功するとリソースが利用可能になり、その時点で、Promise は Response オブジェクトに resolve(解決)されます。

fetch() に then() メソッドをチェーンすることで、Response オブジェクトを引数に受け取って処理することができます。

then() メソッドの第1引数のコールバック関数には Response オブジェクトが渡されます。

fetch('file.txt')
//リクエストが成功すると resolve された Response オブジェクトが引数に渡される
.then(response => {
  //引数に受け取る Response オブジェクトは HTTP レスポンス全体を表すオブジェクト
});

Response オブジェクトはデータ自体ではなく、HTTP レスポンス全体を表すオブジェクトで、そのままではデータとして扱えないので、その内容(JSON や text、FormData など)に応じて Response オブジェクトのメソッドを使って解析(parse)し、内容(種類)に応じたオブジェクトに変換します。

Response オブジェクトのメソッドはいずれも非同期処理で、解析(変換)した結果に解決される Promise を返します(処理が成功すると解析された結果のオブジェクトが返されます)。

以下は同一ディレクトリにあるテキストファイルを読み込んでコンソールに出力する例です。

リクエストが成功すると、最初の then() メソッドのコールバック関数に Response オブジェクトが渡されます。コールバック関数では text() を適用した結果の Promise を return しています。

text() メソッドは response を文字列(テキスト)として解析し、テキストオブジェクトとして resolve される Promise を返す非同期処理のメソッドです。

つまり、text() メソッドの処理が成功すると、続く then() メソッドのコールバック関数に、テキストオブジェクトが渡されます。

fetch('file.txt')
.then(response => {  //response は Response オブジェクト
  //text() は 文字列として resolve される Promise を返す(テキストオブジェクトを返す)
  return response.text();
})
.then((text) => {
  //引数で受け取った text (テキストオブジェクト)の値をコンソールに出力
  console.log(text);
});

async / await を利用

fetch() は async/await を使うと効率よく記述できる場合があり、よく組み合わせて利用されます。

以下は前述の例を async/await を使って書き換えた例です。then() メソッドの代わりに asyncawait を使って記述しています。

await は Async Function(非同期処理関数)の中でのみ動作するキーワードなので、処理を async キーワードを使って Async Function として定義しています。

const myAjax = async () => {
  // レスポンスを取得(await で fetch() が完了するまで待つ)
  const response = await fetch('file.txt');
  // テキストを取得(await で text() が完了するまで待つ)
  const text =  await response.text();
  // テキストをコンソールに出力
  console.log(text);
}
//上記関数を実行
myAjax();

そのまま処理を実行する無名関数(即時関数)使って以下のように記述することもできます。

(async () => {
  // レスポンスを取得(await で fetch() が完了するまで待つ)
  const response = await fetch('file.txt');
  // テキストを取得(await で text() が完了するまで待つ)
  const text =  await response.text();
  // テキストをコンソールに出力
  console.log(text);
})();

以下は上記とほぼ同じですが、引数にファイルの URL を受け取る関数として書き換えた例です。

また、この例では、response の ok プロパティをの値を確認して、404 Not Found や 500 server error の場合はコンソールに警告を出力するようにていています。

const myAjax = async (textFile) => {
  // レスポンスを取得(await で fetch() が完了するまで待つ)
  const response = await fetch(textFile);
  // レスポンスのステータスコードが200番台(成功)の場合
  if (response.ok) {
    // レスポンスからテキストを取得(await で response.text() が完了するまで待つ)
    const text = await response.text();
    // テキストをコンソールに出力
    console.log(text);
  } else {
    // レスポンスのステータスコードが失敗の場合
    console.warn(`リクエスト失敗 status code:${response.status}`);
  }
}
// 上記関数を実行
myAjax('file.txt');

以下は、fetch() が成功した場合は取得したテキストに解決される Promise を返し、失敗した場合は reject される Promise を返す関数として書き換えた例です。

また、関数の呼び出しでは catch() メソッドを使ってエラーを捕捉するようにしています。

// 関数 myAjax は fetch() を実行して Promise を返す
const myAjax = async (textFile) => {
  // レスポンスを取得(await で fetch() が完了するまで待つ)
  const response = await fetch(textFile);
  // レスポンスのステータスコードが200番台(成功)の場合
  if (response.ok) {
    // レスポンスからテキストを取得(await で response.text() が完了するまで待つ)
    const textValue = await response.text();
    // 即時に textValue に resolve される Promise を返す
    return Promise.resolve(textValue);
  } else {
    // 即時に reject される Promise を返す
    return Promise.reject(new Error(`リクエスト失敗 status code:${response.status}`));
  }
}

// 上記関数を実行して返される Promise の then() メソッドを使って処理を行う
myAjax('file.txt')
// 成功した場合は取得したリソースのテキストを出力
.then((text) => {
  console.log(text);
})
// 失敗した場合はエラーを出力
.catch((error) => {
  console.warn(error);
});
// 上記(18〜26行目の部分)は以下のように記述することもできます
myAjax('file.txt').then(console.log).catch(console.log);

参考:MDN Response

Response オブジェクトのメソッド

Response オブジェクトは HTTP レスポンス全体を表すオブジェクトであり、データを取り出すには以下のようなメソッドが用意されています。

レスポンスの種類により、以下のいずれかのメソッドを使って解析し、そのデータ(response body の表すデータ)を取得することができます。

いずれも非同期処理のメソッドで、解析した結果で解決する Promise を返します。メソッドの戻り値を return することで、続く then() メソッドに(解決すると)結果の値が渡されます。

Response オブジェクトのメソッド(抜粋)
メソッド 説明
arrayBuffer() ArrayBuffer として解析した結果で解決(resolve)する Promise を返す
blob() Blob として解析した結果で解決(resolve)する Promise を返す
formData() FormData として解析した結果で解決(resolve)する Promise を返す
json() JSON として解析した結果で解決(resolve)する Promise を返す
text() String(文字列)として解析した結果で解決(resolve)する Promise を返す

「JSON として解析した結果で解決(resolve)する Promise を返す」とは、言い換えると、「戻り値の Promise が解決すると JSON として解析された値が結果になる」ということになります。

以下は、{JSON} Placeholder というテスト用の JSON データを返してくれる REST API にアクセスして、取得したレスポンスを JSON として解析して出力する例です。

この API に GET リクエストで https://jsonplaceholder.typicode.com/posts/1 にアクセスすると、ダミーの1つ目の投稿データの JSON を取得することができます。

fetch() の options の method を省略した場合のリクエストメソッドは GET です。

fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
  //JSON として解析したオブジェクトを返す(JSON で resolve する Promise を返す)
  return response.json();
})
.then((post) => {
  //response.json() が成功したら引数(post)に JSON が渡されるのでその title を出力
  console.log(post.title);
});

Response オブジェクトのプロパティ

Response オブジェクトには、以下のようなプロパティがあります。

Response オブジェクトのプロパティを使って HTTP ステータスをチェックしたり、ヘッダをチェックすることができます。

Response オブジェクトのプロパティ(抜粋)
プロパティ 説明
body 本文のコンテンツの ReadableStream
headers レスポンスに関連した Headers オブジェクト
ok レスポンスが成功(200-299 の範囲のステータス)したか否かを通知する boolean 値
redirected レスポンスがリダイレクトの結果であるかどうかを示します
status HTTP ステータスコード(例:200→ リクエスト成功)
statusText ステータスコードに対応したステータスメッセージ(例:OK → 200の場合)
type レスポンスのタイプ
  • basic:正常な同一オリジンのレスポンス
  • cors:有効なクロスオリジンリクエストからのレスポンス
  • opaque:クロスオリジンへの「no-cors」リクエストへのレスポンス
  • opaqueredirect:redirect: "manual"のリクエストへのレスポンス
url レスポンスの URL

HTTP エラーの制御

HTTP 通信でエラーが発生した場合は、エラーを補足することができます。

ネットワークエラーなどの障害が発生した場合は、reject された Promise が返されるので、then() メソッドの第2引数か catch() メソッドでエラーを捕捉することができます。

以下は catch() メソッドを使ってエラーを補足してエラーの内容をコンソールに出力するコードの例です。

fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((response) => {
  return response.json();
})
.then((post) => {
  console.log(post.title);
})
//エラーの場合の処理
.catch((error) => {
  console.log('Error 発生: ' + error);
});

但し、Promise はネットワーク障害または CORS 違反など許可の問題によってのみ拒否されるため、404 や 500 などの HTTP エラーは拒否されません。

そのため、上記のコードでは指定したファイルが存在しない場合(404)やサーバーエラー(500)などリクエストが正常に終了しない場合には、エラーとして捕捉することができません。

404 Not Found や 500 エラー

ネットワーク障害やブラウザのタイムアウト、CORS 違反などで fetch() が HTTP リクエストを発行できなかった場合、fetch() は reject した Promise を返しますが、404 や 500 のようなエラーレスポンスは成功とみなされてしまいます。

例えば、以下のように API に存在しないリソースの URL を指定して fetch() を実行すると、GET リクエストに対する HTTP エラー(404)は出ますが、Promise は reject しないため、コンソールには「undefined」と出力されてしまいます。

fetch('https://jsonplaceholder.typicode.com/xxxxxxxx')
.then((response) => {
  return response.json();
})
.then((post) => {
  console.log(post.title);  //undefined
})
//エラーの場合の処理(404エラーなどの場合はエラーとして捕捉されない)
.catch((error) => {
  console.log('Error 発生: ' + error);
});

リクエストが成功したかどうかは Response オブジェクトの ok プロパティで判定できます。

ok プロパティは、HTTP ステータスコードが 200〜299 の範囲(成功レスポンス)であれば true を返し、それ以外の400番台や500番台であれば false を返します。

404 や 500 を失敗にしたい場合は、以下のように Response オブジェクトの ok プロパティの値を判定して、response.ok が true でなければエラーをスローするか Promise.reject を返します。

fetch('https://jsonplaceholder.typicode.com/xxxxxxxx')
.then((response) => {
  // ステータスが ok(ステータスコードが200-299の範囲)ならば
  if(response.ok) {
    // レスポンスを JSON として解析
    return response.json();
  } else {
    //ok でなければ例外を発生させ status をエラーメッセージに含めて表示
    throw new Error(`リクエスト失敗: ${response.status}`);
    //または return Promise.reject(new Error(`リクエスト失敗: ${response.status}`));
  }
})
.then((post) => {
  console.log(post.title);
})
//エラーの場合の処理(9行目でスローしたエラーを補足できる)
.catch((error) => {
  console.log(error.message);
});

//出力例(無効な URL を指定した場合)
//リクエスト失敗: 404

エラーにステータスメッセージ(statusText)も含めるには、例えば、上記9行目を以下のように記述します。但し、必ずしもステータスメッセージが取得できるとはかぎらない(空の場合もある)ようです。

throw new Error(`リクエスト失敗:${response.status} ${response.statusText}`);

JSON を取得して HTML を出力

fetch() で JSON データを取得して、そのデータを元に HTML を出力する例です。

JSON ファイルを取得

以下は JSON 形式で記述してある JSON ファイル(リソース)を取得して、そのデータを元に HTML を出力するサンプルです。ファイル構成は以下のようになっています。

任意のディレクトリ
├── users.html //出力用 HTML
├── images
│   ├── bar.png
│   └── foo.png
├── js
│   └── fetch-user-json.js //fetch() を記述する JavaScript
└── users.json  //JSON ファイル
users.json(取得するリソース:JSON ファイル)
[
  {
    "userid": "001",
    "username": "foo",
    "firstName": "Foo",
    "lastName": "Smith",
    "imageURL": "images/foo.png",
    "email": "foo@example.com"
  },
  {
    "userid": "002",
    "username": "bar",
    "firstName": "Bar",
    "lastName": "Jackson",
    "imageURL": "images/bar.png",
    "email": "bar@example.com"
  }
]
users.html(出力用の HTML)
<body>
  <div class="container"></div>
  <script src="js/fetch-user-json.js"></script>
</body>

fetch() メソッドの引数に取得するリソースの JSON ファイル('users.json')を指定して実行します。そしてレスポンスのステータスを確認して、問題なくリソースが取得できれば JSON として解析します。

users.json にはユーザー毎のデータが配列で記述されているので、forEach でユーザーごとの JSON データを処理して HTML を作成して出力します。

この例では何らかの理由でリソースが取得できない場合、コンソールにエラーを出力するだけなので、HTML には何も出力されませんが、必要に応じてエラー時の出力も追加すると良いかと思います。

fetch-user-json.js
fetch('users.json')
.then((response) => {
  //ステータスが ok であればレスポンスを JSON として解析
  if(response.ok) {
    return response.json();
  } else {
    //ステータスが ok でなければエラーにする
    throw new Error(`リクエスト失敗:${response.status} ${response.statusText}`);
  }
})
.then((users) => {
  //出力する HTML 文字列を入れる変数
  let html = '';
  //ユーザーごとの JSON データを処理(users は JSON の配列)
  users.forEach(user => {
    //ユーザーごとの div 要素の HTML
    const userDiv =
      `<div class="user">
        <img src="${user.imageURL}" alt="${user.username}'s image">
        <h3 class="name">${user.firstName} ${user.lastName}</h3>
        <div class="email"><a href="mailto:${user.email}">${user.email}</a></div>
      </div>`;
    //ユーザーごとの div 要素の HTML を変数 html に追加
    html += userDiv;
  });
  //出力先の要素を取得
  const container = document.querySelector('.container');
  //出力先の要素に作成した html を挿入
  container.innerHTML = html;
})
.catch((error) => {
  console.warn(error);
});

以下は上記を async/await を使って記述する例です。

const renderUsers = async (url) => {
  try {
    //await で fetch() が完了するまで待つ
    const response = await fetch(url);
    if(response.ok) {
      //await で response.json() が完了するまで待つ
      const users = await response.json();
      let html = '';
      users.forEach(user => {
        const userDiv =
          `<div class="user">
            <img src="${user.imageURL}" alt="${user.username}'s image">
            <h3 class="name">${user.firstName} ${user.lastName}</h3>
            <div class="email"><a href="mailto:${user.email}">${user.email}</a></div>
          </div>`;
        html += userDiv;
      });
      const container = document.querySelector('.container');
      container.innerHTML = html;
    }else{
      throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
    }

  } catch (error) {
    console.warn(error);
  }
}

renderUsers('users.json');

以下は JSON データを取得する関数と HTML を出力する関数に分けて記述する例です。async/await を使うと比較的簡潔に記述できます。

//JSON データを取得する関数
const getUsers = async (url) => {
  try {
    const response = await fetch(url);
    if(response.ok) {
      return await response.json();
    }else{
      throw new Error(`リクエスト失敗: ${response.status}  ${response.statusText}`);
    }
  } catch (error) {
    console.warn(error);
  }
}

//取得した JSON を元に HTML を出力する関数
const renderUsers = async (url) => {
  const users = await getUsers(url);
  let html = '';
  users.forEach(user => {
    const userDiv =
      `<div class="user">
        <img src="${user.imageURL}" alt="${user.username}'s image">
        <h3 class="name">${user.firstName} ${user.lastName}</h3>
        <div class="email"><a href="mailto:${user.email}">${user.email}</a></div>
      </div>`;
    html += userDiv;
  });
  const container = document.querySelector('.container');
  container.innerHTML = html;
}

renderUsers('users.json');
const getUsers = (url) => {
  //Promise を返す
  return new Promise( (resolve, reject) => {
    fetch(url)
    .then((response) => {
      if(response.ok) {
        return response.json();
      } else {
        throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
      }
    })
    .then((users) => {
      resolve(users);
    })
    .catch((error) => {
      reject(error);
    });
  });
}
/*Async Function は自動的に Promise を返すが、上記の場合は Promise を返すようにする*/

const renderUsers = (url) => {
  getUsers(url).then((users) => {
    let html = '';
    users.forEach(user => {
      const userDiv =
        `<div class="user">
          <img src="${user.imageURL}" alt="${user.username}'s image">
          <h3 class="name">${user.firstName} ${user.lastName}</h3>
          <div class="email"><a href="mailto:${user.email}">${user.email}</a></div>
        </div>`;
      html += userDiv;
    });
    const container = document.querySelector('.container');
    container.innerHTML = html;
  })
}

renderUsers('users.json');

参考サイト:JavaScript Tutorial/JavaScript Fetch API

API から JSON データを取得

以下は {JSON} Placeholder というテスト用の JSON データを返してくれる API から JSON データを取得して、そのデータを元に HTML を出力するサンプルです。

この API では URL にクエリパラメータ(? から始まるクエリ文字列)を指定して、取得するデータをフィルタリングすることができます。例えば、投稿のデータを取得する際に URL に ?userId=1 と指定すれば、userId が 1 の投稿のデータを取得できます。

クエリパラメータを指定しない場合は、リソース(https://jsonplaceholder.typicode.com/posts)にアクセスすると以下のような100件の投稿の JSON データが取得できます。

サンプルでは引数に userId を指定すれば、その userId の投稿のタイトル(title)とコンテンツ(body)を、指定しなければ最初の10件のタイトルとコンテンツを出力します。

[
  {
    "userId": 1,
    "id": 1,
    "title": "sunt aut facere repellat provident occaecati excepturi optio reprehenderit",
    "body": "quia et suscipit\nsuscipit recusandae consequuntur expedita et cum\nreprehenderit molestiae ut ut quas totam\nnostrum rerum est autem sunt rem eveniet architecto"
  },
  {
    "userId": 1,
    "id": 2,
    "title": "qui est esse",
    "body": "est rerum tempore vitae\nsequi sint nihil reprehenderit dolor beatae ea dolores neque\nfugiat blanditiis voluptate porro vel nihil molestiae ut reiciendis\nqui aperiam non debitis possimus qui neque nisi nulla"
  },
  {
    "userId": 1,
    "id": 3,
    "title": "ea molestias quasi exercitationem repellat qui ipsa sit aut",
    "body": "et iusto sed quo iure\nvoluptatem occaecati omnis eligendi aut ad\nvoluptatem doloribus vel accusantium quis pariatur\nmolestiae porro eius odio et labore et velit aut"
  },
  //・・・以下省略・・・
]

出力先の div 要素は予め HTML に記述しておき、fetch() の処理は同じファイルの script タグに記述しています。

また、出力する HTML の要素は createDocumentFragment() を使って空の文書オブジェクト(DocumentFragment)を生成し、タイトルやコンテンツを追加し、最後に HTML に記述してある id="posts" の div 要素に追加します。

<div id="posts"></div><!--出力先の要素-->

<script>
//fetch() で取得したデータから投稿を出力する関数
const renderPosts = (userID) => {
  //データの取得先(リソース)の URL
  let url = 'https://jsonplaceholder.typicode.com/posts';
  //引数の userID が指定されていればクエリ文字をURLに追加
  if(userID) {
    url += '?userId=' + userID;
  }
  //出力先の要素を取得
  const target = document.getElementById('posts');
  //空の文書オブジェクトを生成
  const container = document.createDocumentFragment();
  //出力する投稿数
  let maxPostCount = 10;

  fetch(url)
  .then((response) => {
    if(response.ok) {
      return response.json();
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status} `);
    }
  })
  .then((posts) => {
    const postsCount = posts.length;
    //取得した投稿数の値により出力する投稿数(maxPostCount)を調整
    maxPostCount = postsCount > maxPostCount ? maxPostCount : postsCount;
    //maxPostCount で指定した数のデータをループして各投稿の HTML を生成
    for(let i=0; i<maxPostCount; i++) {
      //各投稿のデータを囲む div 要素
      const div = document.createElement('div');
      div.setAttribute('class', 'post');
      //タイトルを入れる要素
      const title = document.createElement('h3');
      title.setAttribute('class', 'title');
      //コンテンツを入れる要素
      const content = document.createElement('div');
      content.setAttribute('class', 'content');
      //投稿のタイトルを JSON データから取得して要素のテキストに
      title.textContent = `${posts[i].title}`;
      //投稿のコンテンツを JSON データから取得して要素のテキストに
      content.textContent = `${posts[i].body}`;
      div.appendChild(title);
      div.appendChild(content);
      //空の文書オブジェクトに追加
      container.appendChild(div);
    }
    //全ての投稿のデータから要素を生成したら出力先の要素(target)に追加
    const heading = document.createElement('h2'); //全体のタイトル
    //全体のタイトルのテキスト(userID が指定されていればその値を使用)
    heading.textContent = 'Posts ' + (userID ? 'by user :' + userID : '');
    //出力先の要素に全体のタイトルを追加
    target.appendChild(heading);
    //出力先の要素に全ての投稿の要素を追加
    target.appendChild(container);
  })
  .catch((error) => {
    console.warn(error);
  });
}
//引数に userID を指定して上記関数を実行
renderPosts(3);

</script>

上記を実行すると、以下のような HTML が出力されます。

<div id="posts">
  <h2>Posts by user :3</h2>
  <div class="post">
    <h3 class="title">asperiores ea ipsam voluptatibus modi minima quia sint</h3>
    <div class="content">repellat aliquid praesentium dolorem quo
      sed totam minus non itaque
      nihil labore molestiae sunt dolor eveniet hic recusandae veniam
      tempora et tenetur expedita sunt</div>
  </div>
  <div class="post">
    <h3 class="title">dolor sint quo a velit explicabo quia nam</h3>
    <div class="content">eos qui et ipsum ipsam suscipit aut
      sed omnis non odio
      expedita earum mollitia molestiae aut atque rem suscipit
      nam impedit esse</div>
  </div>
  //・・・以下省略・・・
</div>
const renderPosts = async  (userID) => {
  //データの取得先(リソース)の URL
  let url = 'https://jsonplaceholder.typicode.com/posts';
  //引数の userID が指定されていればクエリ文字をURLに追加
  if(userID) {
    url += '?userId=' + userID;
  }
  //出力先の要素を取得
  const target = document.getElementById('posts');
  //空の文書オブジェクトを生成
  const container = document.createDocumentFragment();
  //出力する投稿数
  let maxPostCount = 10;

  try {
    //await で fetch() が完了するまで待つ
    const response = await fetch(url);
    if(response.ok) {
      //await で response.json() が完了するまで待つ
      const posts = await response.json();
      const postsCount = posts.length;
      //取得した投稿数の値により出力する投稿数を調整
      maxPostCount = postsCount > maxPostCount ? maxPostCount : postsCount;
      //maxPostCount で指定した数のデータをループして各投稿の HTML を生成
      for(let i=0; i<maxPostCount; i++) {
        //各投稿のデータを囲む div 要素
        const div = document.createElement('div');
        div.setAttribute('class', 'post');
        //タイトルを入れる要素
        const title = document.createElement('h3');
        title.setAttribute('class', 'title');
        //コンテンツを入れる要素
        const content = document.createElement('div');
        content.setAttribute('class', 'content');
        //投稿のタイトルを JSON データから取得して要素のテキストに
        title.textContent = `${posts[i].title}`;
        //投稿のコンテンツを JSON データから取得して要素のテキストに
        content.textContent = `${posts[i].body}`;
        div.appendChild(title);
        div.appendChild(content);
        //空の文書オブジェクトに追加
        container.appendChild(div);
      }
      //全ての投稿のデータから要素を生成したら出力先の要素(target)に追加
      const heading = document.createElement('h2'); //全体のタイトル
      //全体のタイトルのテキスト(userID が指定されていればその値を使用)
      heading.textContent = 'Posts ' + (userID ? 'by user :' + userID : '');
      //出力先の要素に全体のタイトルを追加
      target.appendChild(heading);
      //出力先の要素に全ての投稿の要素を追加
      target.appendChild(container);
    }else{
      throw new Error(`リクエスト失敗 status code ${response.status}`);
    }
  }catch (error) {
    console.warn(error);
  }
}

//引数に userID を指定して上記関数を実行
renderPosts(4);
OpenWeather API から天気情報を取得

OpenWeather は天気情報を取得できる API で、ユーザー登録すれば無料で利用することができます。

無料で利用するには、Pricing ページの Free の欄の Get API key をクリックして、名前とメールアドレスを登録する必要があります。

ユーザー登録すると API key がメールで送られてくるので、その値をクエリパラメータに指定して天気情報のデータを取得できます。

fetch() に指定する URL には https://api.openweathermap.org/data/2.5/weather? の後に場所(q)や気温の単位(units)、言語(lang)などのクエリパラメータ、及び API key を指定します。デフォルトでは JSON 形式のデータが返されます。

const getWeather = (apiKey) => {
  fetch(`https://api.openweathermap.org/data/2.5/weather?q=Tokyo&units=metric&lang=ja&APPID=${apiKey}`)
  .then((response) => {
    if(response.ok) {
      return response.json();
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status} `);
    }
  })
  .then((data) => {
    console.log(data);
  });
}
//上記関数に API Key を指定して実行
getWeather('be3038xxxxxxxxxxxxxxxxxx68');

エラー処理を省略して async/await を使えば、以下のように記述することもできます。

const getWeather = async (apiKey) => {
  const response = await fetch(`https://api.openweathermap.org/data/2.5/weather?q=Tokyo&units=metric&lang=ja&APPID=${apiKey}`)
  const data = await response.json();
  console.log(data);
}
getWeather('be3038xxxxxxxxxxxxxxxxxx68'); 

上記を実行すると、例えば、コンソールに以下のような値が出力されます。

以下は場所などのパラメータと API Key を別途 params としてオブジェクト形式で定義した例です。

オブジェクト形式のパラメータをクエリ文字列に変換するには URLSearchParams を使います。

const getWeather = (url, params) => {
  //オブジェクト形式のパラメータをクエリ文字列に変換して URL に追加
  url += '?' + ( new URLSearchParams( params ) ).toString();
  fetch(url)
  .then((response) => {
    if(response.ok) {
      return response.json();
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status} `);
    }
  })
  .then((data) => {
    console.log(data);
  });
}

//オブジェクト形式の場所などのパラメータ
const city = {
  q: 'Kyoto',  //場所
  units: 'metric',  //単位(℃ の場合は metric)
  lang: 'ja',  //言語
  APPID: 'be3038xxxxxxxxxxxxxxxxxx68' //API Key
};
const url = 'https://api.openweathermap.org/data/2.5/weather';
getWeather(url, city);

以下は取得した JSON データを元に HTML を出力する例です。

const renderWeather = async (url, params) => {
  url += '?' + ( new URLSearchParams( params ) ).toString();
  try {
    const response = await fetch(url);
    if(response.ok) {
      const data = await response.json();
      const html =
       `<h3>${data.name}の天気</h3>
        <ul>
          <li>気温:${data.main.temp} ℃</li>
          <li>天候: ${data.weather[0].description}</li>
        </ul>`;
      //id が weather の要素に上記の HTML を出力
      document.getElementById('weather').innerHTML = html;
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status} ${response.statusText}`);
    }
  }catch (error) {
    console.warn(error);
  }
}

//オブジェクト形式の場所などのパラメータ
const city = {
  q: 'Morioka',  //場所
  units: 'metric',  //単位(℃ の場合は metric)
  lang: 'ja',  //言語
  APPID: 'be3038xxxxxxxxxxxxxxxxxx68' //API Key
};
const url = 'https://api.openweathermap.org/data/2.5/weather';
renderWeather(url, city);

以下は出力例です。

<div id="weather">
  <h3>盛岡市の天気</h3>
  <ul>
    <li>気温:23.72 ℃</li>
    <li>天候: 曇りがち</li>
  </ul>
</div>

クエリパラメータの mode に html を指定すると、HTMLとしてデータを取得することもできます。

OpenWeather ドキュメント: Current weather data

API key を JavaScript に直接記述しない例

以下は API key を別ファイルに記述しておき、fetch() で API key の値を取得する例です。

fetch() で POST された値を受け取り、その値が openweather であれば API key を返す以下のような PHP ファイルを作成して任意のフォルダに配置します(配置するフォルダに .htaccess で制限をかけてしまうと、fetch() で値を取得できません)。

apikeys.php
<?php
//fetch() で POST された値を受け取る
$input = file_get_contents('php://input');
//受け取った値が openweather であれば API key を返す
if($input === 'openweather') echo 'be3038xxxxxxxxxxxxxxxxxx68';

getApiKey() は引数に指定された値(openweather)を POST メソッドで上記ファイル(apikeys.php)に送信して、API key を取得する関数です。

天気情報を出力する関数では getApiKey() に await を指定して、API key が取得できたら天気情報を取得して出力します。

<div id="weather"></div>

<script>
//引数(data)で指定された API key を取得する関数
const getApiKey = async (data) => {
  const response = await fetch('apikeys.php', {
    method: 'POST',
    headers: { 'Content-Type': 'text/plain' }, //この場合は省略可能
    body: data  //引数で受け取った値
  });
  const apikey = await response.text();
  return apikey;
}

//openweather の エンドポイントと都市名を引数に受け取り天気情報を出力する関数
const renderWeatherHTML = async (url, city) => {
  try {
    //上記関数で API key を取得
    const apiKey = await getApiKey('openweather');
    //オブジェクト形式の場所などのパラメータ
    const params = {
      q: city,  //引数に指定された都市名
      units: 'metric',
      lang: 'ja',
      APPID: apiKey  //関数で取得した API key
    };
    url += '?' + ( new URLSearchParams( params ) ).toString();
    const response = await fetch(url);
    if(response.ok) {
      const data = await response.json();
      const html =
       `<h3>${data.name}の天気</h3>
        <ul>
          <li>気温:${data.main.temp} ℃</li>
          <li>天候: ${data.weather[0].description}</li>
        </ul>`;
      document.getElementById('weather').innerHTML = html;
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status} ${response.statusText}`);
    }
  }catch (error) {
    console.warn(error);
  }
}
//openweather の URL(エンドポイント)
const url = 'https://api.openweathermap.org/data/2.5/weather';
//openweather の URL と天気を取得する都市名を指定
renderWeatherHTML(url, 'New York');

</script>
WordPress の REST API から投稿の情報を取得

WordPress の REST API(WP REST API)は、特定のエンドポイントにアクセスすると、WordPress の投稿などの情報を参照(更新)できるようになっています。

例えば、WordPress が https://example.com/ にインストールされている場合、WordPress の投稿を取得するには以下のような URL(エンドポイント)でアクセスします。

https://example.com/wp-json/wp/v2/posts

WordPress 4.7からは上記の URL にアクセスすることで、WordPress に登録された投稿の情報を参照できるようになっています。

但し、セキュリティ関係のプラグインを有効にしていたり、独自にで REST API を無効にしている場合はアクセスすることができません(関連項目:WP REST API を無効にする)。

例えば、WordPress が https://example.com/ にインストールされていて、WP REST API が有効な場合、以下を実行すると最初の投稿の情報がコンソールに出力されます。

fetch('http://example.com/wp-json/wp/v2/posts')
.then(response => response.json()) //レスポンスを JSON として解析
.then(data => {
  console.log(data[0]);
});

以下でも同じことです。

(async () => {
  const response = await fetch('http://example.com/wp-json/wp/v2/posts');
  const data = await response.json();
  console.log(data[0]);
})();

投稿の情報を JSON 形式にフォーマットしたものが出力されます。タイトルや本文(コンテンツ)、アイキャッチ画像などの情報が含まれています。

上記の場合、data[0].title.rendered で最初の投稿のタイトルの文字列を取得することができます。

以下は WP REST API から投稿データを JSON 形式で取得して出力する例です。

出力先の HTML
<ul id="post-list"></ul>

URL にパラメータを指定することができるので、オブジェクト形式のパラメータを URLSearchParams() でクエリ文字列に変換して URL に追加しています(REST API Handbook/Arguments)。

以下の例ではコンテンツは出力しないので、context に embed を指定して content の内容などを含めないようにして取得する JSON のデータのサイズを小さくしています。

const renderPostLinks = (url, params) => {
  //オブジェクト形式のパラメータをクエリ文字列に変換して URL に追加
  url += '?' + ( new URLSearchParams( params ) ).toString();
  fetch(url)
  .then(response => {
    // ステータスが ok であればレスポンスを JSON として解析
    if(response.ok) {
      return response.json();
    } else {
      throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
    }
  })
  .then(data => {
    //出力先の要素
    const list = document.getElementById('post-list');
    //生成する HTML 文字列
    let html ='';
    //各投稿のタイトル(post.title.rendered)にリンク(post.link)を付けた li 要素を追加
    data.forEach((post) => {
      html += '<li><a href="' + post.link + '">' + post.title.rendered + '</a></li>'
    });
    //出力先の要素の HTML に上記で生成した文字列を設定
    list.innerHTML = html;
  }).catch((error) => {
    console.warn(error.message);
  });
}
//オブジェクト形式のパラメータ
const params = {
  per_page: 20,  //20件表示(デフォルト 10)
  context: 'embed',  //デフォルト 'view'
  order: 'asc',  //逆順に(古い記事から)表示(デフォルト 'desc')
  offset: 3,  //3件の投稿をスキップ(デフォルト 0)
};
//エンドポイントの URL
const url = 'http://example.com/wp-json/wp/v2/posts';
renderPostLinks(url, params);

以下は上記の関数 renderPostLinks を async/await で記述する例です。

const renderPostLinks =  async (url, params) => {
  url += '?' + ( new URLSearchParams( params ) ).toString();
  try {
    const response = await fetch(url);
    if(response.ok) {
      const data = await response.json();
      const list = document.getElementById('post-list');
      let html ='';
      data.forEach((post) => {
        html += '<li><a href="' + post.link + '">' + post.title.rendered + '</a></li>'
      });
      list.innerHTML = html;
    } else {
      throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
    }
  }catch (error) {
    console.warn(error);
  }
} 

取得したレスポンスを DOM として扱う

Response オブジェクトのメソッドには html として解析するメソッドはありません。

そのため、HTML を取得してレスポンスを DOM として扱うには、text() メソッドで文字列として解析した値を DOMParserparseFromString() メソッドを使って Document Object に変換します。

以下が HTMLとして解析する文字列を Document Object に変換する場合の構文です。

DOMParser() コンストラクターで DOMParser を生成して、そのメソッド parseFromString() で変換します。stringContainingHTMLSource は HTMLとして解析する文字列(テキスト)です。

let parser = new DOMParser();
let doc = parser.parseFromString(stringContainingHTMLSource, "text/html");

第2引数に指定する MIME type により XML や SVG の文字列も Document に変換することができます。

MIME type 返される結果
text/xml XMLDocument
image/svg+xml SVGDocument
text/html HTMLDocument

以下は同一サーバー上にある sample.html を取得して、その中の最初の img 要素をコンソールに出力する例です。

fetch() のレスポンスを text() メソッドで文字列として解析し、そのテキストを DOMParser で Document Object に変換しています。

Document Object に変換したら、DOM の メソッドの querySelector() が使えるので、img 要素を取得してコンソールに出力しています。

fetch('sample.html')
.then((response) => {
  if(response.ok) {
    //レスポンスをテキストに変換
    return response.text();
  } else {
    throw new Error(`リクエスト失敗:${response.status}`);
  }
})
.then((htmlText) => {
  // HTML 文字列を Document Object に変換
  const parser = new DOMParser();
  const htmlDOM = parser.parseFromString(htmlText, 'text/html');

  // DOM に変換した htmlDOM で querySelector() を実行して img 要素を取得
  const imgElem = htmlDOM.querySelector('img');
  // 取得した img 要素をコンソールに出力
  console.log(imgElem);

}).catch( (error) => {
  // エラーがあれば出力
  console.warn(error);
});
特定の要素を取得して出力

レスポンスの HTML から特定の要素を取得して出力することもできます。

以下は fetch() で取得した HTML から querySelector() で要素を抽出して、HTML に出力する例です。

<div id="output"></div><!--出力先要素-->

<script>
/*
指定された HTML ファイルから要素を抽出して出力先要素に追加する関数
引数
url :取得する HTML ファイルの URL(パス)
targetElem:抽出する要素のセレクタ
outputElem:出力先要素のセレクタ
*/
const renderFetchedHTML = (url, targetElem, outputElem) => {

  fetch(url)
  .then((response) => {
    if(response.ok) {
      //レスポンスをテキストに変換
      return response.text();
    } else {
      throw new Error(`リクエスト失敗:${response.status}`);
    }
  })
  .then((htmlText) => {
    // DOMParser を生成
    const parser = new DOMParser();
    // 取得した(テキストから変換された) HTML の Document Object
    const htmlDOM = parser.parseFromString(htmlText, 'text/html');
    return htmlDOM;
  })
  .then((html) => {
    // 抽出する要素を取得
    const elem = html.querySelector(targetElem);
    if(elem) {
      // 出力先に抽出した要素を追加
      document.querySelector(outputElem).appendChild(elem);
    }
  })
  .catch( (error) => {
    reject(error);
  });
}
//sample.html の id が target の要素を id が output の要素に追加して表示(出力)
renderFetchedHTML('../sample.html', '#target', '#output' );
</script>

以下は、上記を「フェッチして Document Object に変換する関数」と「要素を抽出して出力先に追加する関数」に分けた例です。

// HTML のテキストを取得して Document Object に変換する関数
const fetchHTML = async (url) => {
  try {
    const response = await fetch(url);
    if(response.ok) {
      const htmlText = await response.text();
      const parser = new DOMParser();
      const htmlDOM = parser.parseFromString(htmlText, 'text/html');
      return htmlDOM; //Async Funtion では自動的に Promise が返される
    }else{
      throw new Error(`リクエスト失敗: ${response.status}  ${response.statusText}`);
    }
  } catch (error) {
    console.log(error);
  }
}

//fetchHTML() でフェッチした Document Object から要素を抽出して出力先に追加する関数
const renderFetchedHTML = async (url, targetElem, target) => {
  const html = await fetchHTML(url);
  const elem = html.querySelector(targetElem);
  if(elem) {
    document.querySelector(target).appendChild(elem);
  }
}
const fetchHTML = (url) => {
  //Promise を返す
  return new Promise( (resolve, reject) => {
    fetch(url)
    .then((response) => {
      if(response.ok) {
        //レスポンスをテキストに変換
        return response.text();
      } else {
        throw new Error(`リクエスト失敗:${response.status}`);
      }
    })
    .then((htmlText) => {
      // HTML 文字列を Document Object に変換
      const parser = new DOMParser();
      const htmlDOM = parser.parseFromString(htmlText, 'text/html');
      //成功した場合(結果の値を resolve)
      resolve(htmlDOM);
    }).catch( (error) => {
      //失敗した場合
      reject(error);
    });
  });
}

const renderFetchedHTML = (url, targetElem, outputElem) => {
  fetchHTML(url).then((html) => {
    const elem = html.querySelector(targetElem);
    if(elem) {
      document.querySelector(outputElem).appendChild(elem);
    }
  })
}
OpenWeather の API からデータを HTML として取得

以下は OpenWeather の API からデータを HTML として取得する例です。

(async () => {
  //クエリパラメータで mode=html を指定して HTML で取得
  const response = await fetch('https://api.openweathermap.org/data/2.5/weather?q=Tokyo&units=metric&lang=ja&mode=html&APPID=be3038xxxxxxxxxxxxxxxxxx68')
  const data = await response.text();
  console.log(data); //コンソールに出力
})();

以下のようなテキストがコンソールに出力されます。天気の情報は body 直下の div 要素に記述されているので、querySelectorAll('body div') で抽出することができます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="keywords" content="weather, world, openweathermap, weather, layer" />
  <meta name="description" content="A layer with current weather conditions in cities for world wide" />
  <meta name="domain" content="openweathermap.org" />
  <meta http-equiv="pragma" content="no-cache" />
  <meta http-equiv="Expires" content="-1" />
</head>
<body>
  <!--以下の div 要素を querySelectorAll('body div') で全て抽出-->
  <div style="font-size: medium; font-weight: bold; margin-bottom: 0px;">東京都</div>
  <div style="float: left; width: 130px;">
    <div style="display: block; clear: left;">
      <div style="float: left;" title="Titel">
        <img height="45" width="45" style="border: medium none; width: 45px; height: 45px; background: url(&quot;http://openweathermap.org/img/w/01n.png&quot;) repeat scroll 0% 0% transparent;" alt="title" src="http://openweathermap.org/images/transparent.png"/>
      </div>
      <div style="float: left;">
        <div style="display: block; clear: left; font-size: medium; font-weight: bold; padding: 0pt 3pt;" title="Current Temperature">21.32°C</div>
        <div style="display: block; width: 85px; overflow: visible;"></div>
      </div>
    </div>
    <div style="display: block; clear: left; font-size: small;">Clouds: 0%</div>
    <div style="display: block; clear: left; color: gray; font-size: x-small;" >Humidity: 50%</div>
    <div style="display: block; clear: left; color: gray; font-size: x-small;" >Wind: 6.69 m/s</div>
    <div style="display: block; clear: left; color: gray; font-size: x-small;" >Pressure: 1016hpa</div>
  </div>
  <div style="display: block; clear: left; color: gray; font-size: x-small;">
    <a href="http://openweathermap.org/city/1850144?utm_source=openweathermap&utm_medium=widget&utm_campaign=html_old" target="_blank">More..</a>
  </div>
</body>
</html>

以下は OpenWeather の API からデータを HTML として取得して表示する例です。

JSON ファイルを取得する例と内容的にはほぼ同じですが、クエリパラメータに mode=html を指定して HTML で取得しています。

出力先の HTML
<div id="weather"></div>
const renderWeatherHTML = async (url, params) => {
  //オブジェクト形式のパラメータをクエリ文字列に変換して URL に追加
  url += '?' + ( new URLSearchParams( params ) ).toString();
  try {
    const response = await fetch(url);
    if(response.ok) {
      //テキストを取得
      const data = await response.text();
      console.log(data);
      const parser = new DOMParser();
      //取得したテキストを Document Object に変換
      const htmlDOM = parser.parseFromString(data, 'text/html');
      //天気情報が記述されている全ての div 要素を取得
      const divs = htmlDOM.querySelectorAll('body div');
      //出力先の要素
      const weatherDiv = document.getElementById('weather');
      //天気情報が記述されている全ての div 要素を出力先要素に追加
      divs.forEach((div) => {
        weatherDiv.appendChild(div);
      })
    } else {
      throw new Error(`リクエスト失敗 status code ${response.status}`);
    }
  }catch (error) {
    console.warn(error);
  }
}

const city = {
  q: 'Nagano',  //場所
  units: 'metric',  //単位(℃ の場合は metric)
  lang: 'ja',  //言語
  mode: 'html', //リソースのフォーマットを HTML に指定
  APPID: 'be3038xxxxxxxxxxxxxxxxxx68'
};
const url = 'https://api.openweathermap.org/data/2.5/weather';
renderWeatherHTML(url, city);

以下は出力例です。

POST メソッド

POST メソッドでリクエストをサーバーに送る場合は、第2引数の options の method に POST を指定し、body に送信するリクエストの本体(本文)を記述します。

必要に応じて headers にリクエストのヘッダーを記述して追加します。。

body のデータ型はヘッダーの Content-Type と一致する必要があります。body がプレインなテキストの場合は Content-Type にはデフォルトで text/plain が設定されます。

第2引数のパラメータ(options)一部抜粋
options 指定する値
method リクエストメソッド。POST を指定します。
headers リクエストに追加したいヘッダーを指定します。'Content-Type': 'application/json'のようなオブジェクトリテラル、または Headers オブジェクトを指定します。
body リクエストの本文(リクエストボディ)を指定します。本文のデータ型は headers の Content-Type と一致する必要があります。

GET リクエストの場合は、リクエストの内容はクエリパラメータに記述しますが、POST の場合は body に指定します。

JSON 形式(application/json)で送信

以下は JSON データ(JSON 形式のオブジェクト)を POST メソッドで送信する例です。

//送信するデータ(body に指定する際に JSON.stringify() で JSON 形式の文字列にに変換)
const data = {
  name: 'Foo',
  id: '001'
};

//第2引数のパラメータ(options)
const params  = {
  //POST を指定
  method: 'POST',
  headers: {
    //JSON 形式のデータを送るので、Content-Type に application/json を指定
    "Content-Type": 'application/json'
  },
  //送信する JSON データをシリアライズ(JSON 文字列に変換)してリクエストボディに指定
  body: JSON.stringify(data)
};

fetch('post.php', params);

fetch() の引数に直接パラメータを指定して記述すると以下のようになります。

fetch('post.php', {
  //POST を指定
  method: 'POST',
  headers: {
    //JSON 形式のデータを送るので、Content-Type に application/json を指定
    "Content-Type": 'application/json'
  },
  //送信する JSON データ(指定する際に JSON 文字列に変換)
  body: JSON.stringify({
    name: 'Foo',
    id: '001'
  })
})

レスポンスの取得は、GET で送信する場合と同じです。取得するデータの内容に合わせて Response オブジェクトのメソッドで解析して変換します。

以下の場合は、レスポンスはテキストとして返されることを想定している(サーバー側でテキストで返す)ので text() メソッドを使用しています。

fetch('post1.php', {
  method: 'POST',
  headers: {
    "Content-Type": 'application/json'
  },
  body: JSON.stringify({
    name: 'Foo',
    id: '001'
  })
})
.then((response) => {
  if(response.ok) {
    //ステータスが ok ならばレスポンスボディを取得して変換
    return response.text();
    //return response.json();
  } else {
    //ok でなければ例外を発生
    throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
  }
})
.then((text) => {
  //データ(この場合はテキスト)を出力
  console.log(text);
})
.catch((error) => {
  console.warn(error);
});

サーバ側(PHP)

以下はサーバ側(PHP)の例です。

PHP ではフォームで POST 送信したデータは $_POST で取得できますが、 $_POST で POST されたデータを受け取れるのはコンテントタイプが application/x-www-form-urlencoded または multipart/form-data の場合に限られます。

Content-Type に application/json を指定した場合は $_POST で取得できないので、file_get_contents() の引数に php://input を指定してリクエストのデータを取得します。

php://input を指定すると、リクエストの body 部から生のデータを読み込むことができます。

取得した値は json_decode() で PHP の変数に変換して、何からの処理をすることができます。

値を echo して返すと、fetch() でレスポンスボディとして受け取ることができます。この例では、テキストとして返しているので、上記 fetch() のレスポンスの取得では text() メソッドを使用しています。

post1.php
<?php
//fetch() で POST された値(JSON 文字列)を受け取る
$json = file_get_contents("php://input");

//JSON 文字列を PHP の変数に変換(true を指定して連想配列形式に)
$data = json_decode($json, true);

//変数に代入
$name = $data["name"];
$id = $data["id"];

//何らかの処理(この例では文字を追加)
$name = $name.'さん';;
$id = $id.'番';

//echo して返すと、fetch() でレスポンスボディとして受け取られる
echo "Name: {$name} ID: {$id} "; //テキストとして返す

以下は json_encode() を使って値を JSON 形式で返す場合の例です。

この場合、fetch() のレスポンスの取得では json() メソッドを使用して変換します。

<?php
$json = file_get_contents("php://input");
$data = json_decode($json, true);
$name = $data["name"];
$id = $data["id"];
//何らかの処理
$data["name"] = $name.'さん!';;
$data["id"] = $id.'番';

//JSON 形式にして返す
echo json_encode($data);
FormData に JSON データを含めて送信

前述の例ではリクエストボディ(オプションの body)に JSON データを指定して送信しましたが、FormData に JSON データを追加して送信することもできます。

fetch() や XMLHttpRequest のリクエストボディには FormData オブジェクトを指定することができ、FormData を使うと、キーと値のペアのセットの形式でデータをまとめて送信することができます。

また、リクエストボディに FormData オブジェクトを指定する場合は、追加のリクエストヘッダーの設定は必要ありません(POST される Content-Type は multipart/form-data になります)。

以下では JSON に変換したデータを生成した FormData オブジェクトに append() メソッドでキーと値のペアとして追加して、リクエストボディに指定してサーバーに送信しています。

この例ではサーバーからは JSON 形式で返されるので、response.json() で JSON として解析します。

// オブジェクト形式のデータ
const data = {
  name: 'Foo',
  id: '001'
}
// JSON 文字列に変換
const jsonData = JSON.stringify(data);

// FormData のインスタンスを生成
const formData = new FormData();
// キー json と値 json_data のペアを追加
formData.append('json',jsonData );

fetch('post2.php', {
  method: 'POST',
  body: formData // リクエストボディに FormData オブジェクトを指定
})
.then((response) => {
  if(response.ok) {
    //ステータスが ok ならばレスポンスボディを取得して JSON に変換
    return response.json();
  } else {
    //ok でなければ例外を発生
    throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
  }
})
.then((json) => {
  //データ(JSON)を出力
  console.log(json);
})
.catch((error) => {
  console.warn(error);
});

URLSearchParams を使う

FormData の代わりに、URLSearchParams を使うこともできます。

以下は URLSearchParams を使って書き換えた例です。URLSearchParams も FormData と同様のメソッド append() を持っています。

上記との違いは、8行目の URLSearchParams インスタンスの生成と代入した変数名(10行目、14行目)の部分のみです。

const data = {
  name: 'Foo',
  id: '001'
}
const jsonData = JSON.stringify(data);

// URLSearchParams のインスタンスを生成
const params = new URLSearchParams();
// キー json と値 json_data のペアを追加
params.append('json',jsonData );

fetch('post2.php', {
  method: 'POST',
  body: params // リクエストボディに URLSearchParams オブジェクトを指定
})
.then((response) => {
  if(response.ok) {
    return response.json();
  } else {
    throw new Error(`リクエスト失敗: ${response.status} ${response.statusText}`);
  }
})
.then((json) => {
  console.log(json);
})
.catch((error) => {
  console.warn(error);
});

サーバ側(PHP)

以下はサーバ側(PHP)の例です。この場合、前述の例とは異なり、コンテントタイプは multipart/form-data なので、 $_POST でデータを取得することができます。

post2.php
<?php
// $_POST[] のキーに 'json' を指定して JSON データを取得
$json = $_POST['json'];
// JSON 文字列を PHP の変数に変換(true を指定して連想配列形式に)
$data = json_decode($json, true);

$name = $data["name"];
$id = $data["id"];

$data["name"] = $name.'さん!';;
$data["id"] = $id.'番';

//JSON 形式にして返す
echo json_encode($data);

フォームを FormData を使って送信

以下は fetch() を使ってフォームのデータを FormData() を使って POST メソッドで送信する例です。

fetch() を使ったフォームの送信は非同期なので、通常のフォームの送信と異なり、ページの再読み込みが発生しません(デフォルトの送信動作をキャンセルする必要があります)。

オプションの method に POST を指定し、フォームのデータは FormData() の引数に form 要素を指定して FormData オブジェクトを生成してオプションの body に指定します。

FormData を使う場合、POST される Content-Type は multipart/form-data になり、リクエストヘッダーを使ってコンテントタイプを指定する必要はありません。

フォームのボタンをクリックするか、入力欄にカーソルを置いた状態で return キーをクリックすると、入力された名前とメールアドレスの値を PHP ファイル(サーバー)に送信されます。

そして、そのレスポンス(PHP ファイルで echo された値)を取得して id="response" の p 要素に出力します。

以下ではフォームの submit イベント(フォームのボタンをクリックしたり、return キーが押されると発生)を使って処理を記述しています。そのため、e.preventDefault() でフォームのデフォルトの動作(通常の送信とページの再読み込み)をキャンセルして非同期に送信します。

<form id="fetchForm">
  <div>
    <label for="name">名前 </label>
    <input type="text" name="name" id="name">
  </div>
  <div>
    <label for="email1">メールアドレス </label>
    <input type="email" id="email" name="email">
  </div>
  <button id="send">Send</button>
</form>
<p id="response"></p>

<script>

// フォームに submit イベントのリスナーを設定
document.getElementById('fetchForm').addEventListener('submit', (e) => {
  // デフォルトのフォームの送信をキャンセル
  e.preventDefault();

  //form 要素から FormData オブジェクトを生成
  const data = new FormData(document.getElementById('fetchForm'));

  fetch('fetch_post2.php', {
    method: 'POST',  //POST メソッド
    body: data,  //FormData オブジェクト
  })
  .then((response) => {
    if(response.ok) {
      // ステータスが ok ならばレスポンスボディを取得してテキストに
      return response.text();
    } else {
      //ok でなければ(404エラーなどの場合)例外を発生
      throw new Error(`失敗(例外発生): ${response.status}`);
    }
  })
  .then((text) => {
    // PHPファイルから取得したテキストを id="response" の p 要素に出力
    document.getElementById('response').textContent = text;
  })
  .catch((error) => {
    // エラーの場合はエラーを id="response" の p 要素に出力
    document.getElementById('response').textContent = error;
  });

});
</script>

サーバ側(PHP)

以下はサーバ側(PHP)の例です。

この例では、単に受け取った値に文字列を追加して echo して返しています。

fetch_post2.php
<?php
//受け取った値
$name = trim( filter_input(INPUT_POST, 'name') );
$email = trim( filter_input(INPUT_POST, 'email') );

//何らかの処理(省略)

//echo して返す → fetch() でレスポンスボディとして受け取られる
echo "「名前」{$name} 「メールアドレス」{$email} を受け取りました。";

実際にはフォームに入力された値を検証するなどの処理が必要になります。

<form id="fetchForm">
  <div>
    <label for="name">名前 </label>
    <input class="required maxlength" data-maxlength="30" type="text" name="name" id="name">
  </div>
  <div>
    <label for="email1">メールアドレス </label>
    <input class="required email" type="email" id="email" name="email" size="30">
  </div>
  <button id="send" >Send</button>
</form>
<p id="response"></p>
<script>
//入力された値を検証する関数
const validateForm = (id) => {
  const form = document.getElementById(id);
  if(form) {
    try {
      const requiredElems = document.querySelectorAll('.required');
      const emailElems =  document.querySelectorAll('.email');
      const maxlengthElems =  document.querySelectorAll('.maxlength');
      const createError = (elem, errorMessage) => {
      const errorSpan = document.createElement('span');
        errorSpan.classList.add('error');
        errorSpan.setAttribute('aria-live', 'polite');
        errorSpan.textContent = errorMessage;
        elem.parentNode.appendChild(errorSpan);
      }
      form.querySelectorAll('.error').forEach( (elem) => {
        elem.remove();
      });
      requiredElems.forEach( (elem) => {
        const elemValue = elem.value.trim();
        if(elemValue.length === 0) {
          createError(elem, '入力は必須です');
        }
      });
      emailElems.forEach( (elem) => {
        const pattern = /^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/ui;
        if(elem.value !=='') {
          if(!pattern.test(elem.value)) {
            createError(elem, 'Email アドレスの形式が正しくないようです。');
          }
        }
      });
      maxlengthElems.forEach( (elem) => {
        const maxlength = elem.dataset.maxlength;
        if(elem.value !=='') {
          if(elem.value.length > maxlength) {
            createError(elem, maxlength + '文字以内でご入力ください');
          }
        }
      });

      if(form.querySelectorAll('.error').length > 0) {
        throw new Error(`入力値の検証で問題があります`);
      }
    }catch (error) {
      console.warn(error);
      return false;
    }
    return true;
  }
}

// フォームに submit イベントのリスナーを設定
document.getElementById('fetchForm').addEventListener('submit', (e) => {
  e.preventDefault();
  //入力値の検証が問題なければ fetch() を実行
  if(validateForm('fetchForm')) {
    //form 要素から FormData オブジェクトを生成
    const data = new FormData(document.getElementById('fetchForm'));

    fetch('fetch_post2.php', {
      method: 'POST',  //POST メソッド
      body: data,  //リクエストボディ
    })
    .then((response) => {
      if(response.ok) {
        // ステータスが ok ならばレスポンスボディを取得してテキストに
        return response.text();
      } else {
        //ok でなければ(404エラーなどの場合)例外を発生
        throw new Error(`失敗(例外発生): ${response.status}`);
      }
    })
    .then((text) => {
      // PHPファイルから取得したテキストを id="response" の p 要素に出力
      document.getElementById('response').innerText = text;
      document.getElementById('fetchForm').reset();
    })
    .catch((error) => {
      // エラーの場合はエラーを id="response" の p 要素に出力
      document.getElementById('response').textContent = error;
    });
  }
});
</script>
<?php
//不正な値がないかを検証する関数
function checkInput($var){
  if(is_array($var)){
    return array_map('checkInput', $var);
  }else{
    //NULLバイト攻撃対策
    if(preg_match('/\0/', $var)){
      die('不正な入力です。');
    }
    //文字エンコードのチェック
    if(!mb_check_encoding($var, 'UTF-8')){
      die('不正な入力です。');
    }
    //改行、タブ以外の制御文字のチェック
    if(preg_match('/\A[\r\n\t[:^cntrl:]]*\z/u', $var) === 0){
      die('不正な入力です。制御文字は使用できません。');
    }
    return $var;
  }
}
//POST された値を上記関数で検証
$_POST = checkInput( $_POST );

POST された値を変数に格納
$name = trim( filter_input(INPUT_POST, 'name') );
$email = trim( filter_input(INPUT_POST, 'email') );

$error = array();
if ( $name == '' ) {
  $error[ 'name' ] = '*お名前は必須項目です。';
} else if ( preg_match( '/\A[[:^cntrl:]]{1,30}\z/u', $name ) == 0 ) {
  $error[ 'name' ] = '*お名前は30文字以内でお願いします。';
}
if ( $email == '' ) {
  $error[ 'email' ] = '*メールアドレスは必須です。';
} else {
  $pattern = '/^([a-z0-9\+_\-]+)(\.[a-z0-9\+_\-]+)*@([a-z0-9\-]+\.)+[a-z]{2,6}$/uiD';
  if ( !preg_match( $pattern, $email ) ) {
    $error[ 'email' ] = '*メールアドレスの形式が正しくありません。';
  }
}

if ( count( $error ) === 0 ) {
  $name = htmlspecialchars($name, ENT_QUOTES, 'UTF-8');
  $email = htmlspecialchars($email, ENT_QUOTES, 'UTF-8');
  echo "「名前」{$name} 「メールアドレス」{$email} で登録しました(実際には登録していません)。";
} else {
  $message = '';
  if(isset($error[ 'name' ])) {
    $message .= $error[ 'name' ];
  }
  if(isset($error[ 'email' ])) {
    $message .= $error[ 'email' ];
  }
  echo "登録できませんでした。{$message}";
}

以下は検証を追加したサンプルです。

関連ページ:

Promise

プロミス (Promise) は非同期処理を扱うための仕組みです。

関連ページ:Promise / Async Function の使い方(非同期処理)

Promise の大まかな仕組み

以下は Promise を使って非同期処理を行う場合の概要です。

Promise オブジェクトを返す関数を定義し、その関数を使って Promise のインスタンスを生成します。そして Promise のインスタンスの then() メソッドにコールバック関数を登録します。

Promise オブジェクトは new 演算子とコンストラクタ Promise(executor) で生成します。

コンストラクタの引数 executor は2つの引数 (resolve, reject) を受け取る関数で、通常以下のように無名関数として指定します。

この関数の中で必要な処理を記述し、処理が成功した場合は第1引数の resolve を呼び出し、結果の値 value を引数に指定します。

失敗した場合は第2引数の reject を呼び出し、エラーオブジェクト(error)を引数に指定します。

定義した関数を実行して Promise オブジェクトを生成し、その then() メソッドに非同期処理が成功した場合と失敗した場合のコールバック関数を登録します。

これにより、何らかの非同期処理が成功した場合は then() メソッドの第1引数に登録したコールバック関数が呼び出され、引数として resolve() に指定した値 value が渡されます(resolve します)。

失敗した場合は then() メソッドの第2引数に登録した関数が呼び出され、引数として reject() に指定した error が渡されます(reject します)。

//Promise を返す関数を定義
function myPromise(args) {
  //Promise を生成して返す
  return new Promise( (resolve, reject) => {
    //何からの非同期処理(コールバック関数に渡したい値:value)

    /*成功した場合→値(value)を引数に指定して第1引数の resolve を呼び出す */
    resolve(value);

    /*失敗した場合→エラー(error)を引数に指定して第2引数の reject を呼び出す */
    reject(error);
  });
}

//上記で定義した関数を実行して Promise オブジェクトを生成
const promise = myPromise(args);

//then() メソッドにコールバック関数を定義
promise.then(
  //第1引数(成功時に value を受け取るコールバック関数)
  (value) => { /* 成功した場合の処理 */ },
  //第2引数(失敗時に error を受け取るコールバック関数)
  (error) => { /* 失敗した場合の処理 */ }
);

上記では一度関数を実行して Promise を生成して変数に代入し(16行目)、19行目で then() メソッドを実行していますが、Promise の生成に続けて then() メソッドをチェーンすることもできます。

//Promise を返す関数を定義
function myPromise(args) {
  //Promise を生成して返す
  return new Promise( (resolve, reject) => {
    // 何からの非同期処理
    /* 成功した場合 */
    resolve(value);
    /* 失敗した場合 */
    reject(error);
  });
}

//上記関数を実行して Promise を生成し、続けて then() メソッドを実行(チェーン)
myPromise(args).then(
  (value) => { /* 成功した場合の処理 */ }, //第1引数
  (error) => { /* 失敗した場合の処理 */ }  //第2引数
);

fetch() の場合

fetch() は Promise オブジェクトを返すので、上記の Promise を返す関数 myPromise() に該当します。

fetch() は HTTP リクエストを送信してデータの取得(非同期処理)を行います。

リクエストが完了すると、リソース(データ)が利用可能になります。 この時点で、Promise は Response オブジェクト(response)に resolve されます。

そして取得した response を then() メソッドの第1引数のコールバック関数に渡して呼び出します。

データの取得に失敗したらエラーを then() メソッドの第2引数のコールバック関数に渡して呼び出します。

fetch(url,init).then(
  (response) => { /* 成功した場合の処理 */ }, //第1引数
  (error) => { /* 失敗した場合の処理 */ }  //第2引数
);

then() メソッドは成功した場合の処理(第1引数)のみを記述して、処理した値を return することで Promise をチェーンすることができます。

また、エラーは catch() メソッドを使って捕捉することができるので、以下のように記述して使うこともできます。

fetch(url,init).then(
  (response) => { /* 成功した場合の処理 */ return value1; }
).then(
  (value1) => { /* 成功した場合の処理 */ return value2; }
).then(
  (value2) => { /* 成功した場合の処理 */ }
).catch(
  //失敗した場合にエラーを捕捉
  (error) => { /*失敗した場合に実行する処理*/ }
); ;

以下の場合、fetch() メソッドが成功すると、取得した結果(Response オブジェクト)は response として最初の then() メソッドのコールバックに渡されます。

言い換えると、fetch() が返す Promise は結果の Response オブジェクトに resolve されます。

最初の then() メソッドでは受け取った Response オブジェクトの json() メソッドを使って JSON オブジェクトとしてパースしてその値を return して次の then() メソッドのコールバックに渡しています。

言い換えると、Response オブジェクトの json() メソッドが返す Promise は Response オブジェクトを JSON としてパースしたオブジェクトで resolve されます。

続く then() メソッドではパースした値を data として受け取ってコンソールに出力しています。

fetch('https://jsonplaceholder.typicode.com/users')
//fetch() が成功すると取得した結果が then() の第1引数の処理に渡される
.then((response) => {
  //Response オブジェクトの json() を使って JSON オブジェクトとしてパースして return
  return response.json();
})
//json() のパース処理が成功するとその結果が次の then() の第1引数の処理に渡される
.then((data) => {
  console.log(data);
})
//もし、途中でエラーが発生すると以下で捕捉される
.catch(
  (error) => console.log(error.message)
);

以下はコメントを削除したコードで、実際にはとてもシンプルです。

fetch('https://jsonplaceholder.typicode.com/users')
.then((response) => {
  return response.json();
})
.then((data) => {
  console.log(data);
})
.catch(
  (error) => console.log(error.message)
);

async/await で書き換え

以下は上記を async/await を使って書き換えた例です。

const myAsync = async (url) => {
  const response = await fetch(url);
  const data = await response.json();
  console.log(data);
}

myAsync('https://jsonplaceholder.typicode.com/users')
.catch(
  (error) => console.log(error.message)
);

await を指定すると、指定した非同期関数の Promise が確定してその結果が返されるまで、次の処理を待たせることができます。

但し、await は Async Function の関数の中でのみ動作するので、この例では async キーワードを使って Async Function を定義しています。

4行目では fetch() に await を指定しているので、fetch() が返す Promise が確定してその結果が返されるまで次の行の処理に移りません。

fetch() の処理が完了すると、次の await を指定した response.json() が実行されます。こちらも response.json() が返す Promise が確定してその結果が返されるまで次の行の処理に移りません。

response.json() の処理が完了すると、console.log(data) が実行されます。

//Async Function として定義
const myAsync = async (url) => {
  //await を指定→ fetch() の処理が完了するまで次の処理を待つ
  const response = await fetch(url);
  //await を指定→ response.json() の処理が完了するまで次の処理を待つ
  const data = await response.json();
  //以下は上記の処理が完了するのを待って実行される
  console.log(data);
}

myAsync('https://jsonplaceholder.typicode.com/users')
//もし、途中でエラーが発生すると以下で捕捉
.catch(
  (error) => console.log(error.message)
);

try...catch 構文を使って以下のように記述することもできます。

const myAsync = async (url) => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const data =  await response.json();
    console.log(data);
  } catch (error) { // エラーを捕捉
    console.log(error.message);
  }
}

myAsync('https://jsonplaceholder.typicode.com/users');

また、即時関数を使って以下のように記述することもできます。

(async () => {
  try {
    const response = await fetch('https://jsonplaceholder.typicode.com/users');
    const data =  await response.json();
    console.log(data);
  } catch (error) {
    console.log(error.message);
  }
})()

Promise() コンストラクター

Promise オブジェクトを生成するコンストラクタには executor と呼ばれる関数を渡し、何からの非同期処理を定義します。

Promise オブジェクトのコンストラクタ
var promise = new Promise(executor)

executor は resolve と reject という2つの関数を引数に受け取ります。 resolve と reject のどちらかのみを受け取ることもできます。また、これらの引数には好きな名前を付けることができます。

var promise = new Promise( (resolve, reject) => {
  // 非同期処理(executor の処理)を記述
});

executor では何らかの処理を行い、その処理結果により引数で受け取った関数を呼び出します。

  • 成功した場合 → 第1引数の resolve() を呼び出す(引数には結果の値を指定)
  • 失敗した場合 → 第2引数の reject() を呼び出す(引数には Error オブジェクトを指定)
var promise = new Promise( (resolve, reject) => {
  // 何からの非同期処理
  // 処理による結果の値(コールバック関数に渡したい値):value

  /* 成功した場合 */
  //結果の値を引数に指定して第1引数の resolve を呼び出す
  resolve(value);

  /* 失敗した場合 */
  //エラーオブジェクトを引数に指定して第2引数の reject を呼び出す
  reject(Error オブジェクト);

});

then() メソッド

promise オブジェクトには promise の状態が遷移した時(処理が成功または失敗した時)に呼ばれるコールバック関数を登録(定義)するための promise.then() というメソッドがあります。

then() メソッドに関数を登録(定義)することで、処理が成功した場合や失敗した場合にその登録した関数が呼び出されて実行されます。

then() メソッドの第1引数には処理が成功した場合のコールバック関数(onFulfilled)を、第2引数には処理が失敗した場合のコールバック関数(onRejected)を指定します。

promise.then(onFulfilled, onRejected)

以下のように直接関数を定義することができます。

promise.then(
  (value) => { /*成功した場合に実行する処理*/ }, //第1引数(onFulfilled)
  (error) => { /*失敗した場合に実行する処理*/ } //第2引数(onRejected)
);
第1引数(onFulfilled)
非同期処理が成功したときに実行される関数。引数には結果(value)を受け取る
第2引数(onRejected)
非同期処理が失敗したときに実行される関数。引数にはエラー(error)を受け取る

以下は Promise の動作を確認するためのサンプルです(実用的なものではありません)。

関数 myPromise() は引数 min に指定された値と乱数を使って生成した値 rand を比較して、rand が min 以上であれば成功と判定し、rand や min、string の値からなる文字列を resolve() の引数に渡して呼び出します。

失敗した場合は、rejecte() の引数にエラーオブジェクトを渡して呼び出します。

then() メソッドでは、成功した時のコールバックと失敗した時のコールバックを登録しています。

以下の場合、成功した場合は例えば「Hello (rand: 729 >= min: 300)」、失敗した場合は「失敗。(rand: 47 < min: 300)」などとコンソールに出力されます。

/*
引数
string:出力する文字列
min:1〜999の間の数値(乱数で作成した値がこの値以上なら成功とする)*/

//上記の引数を受け取り Promise を生成して返す関数
const myPromise = (string, min) => {
  //Promise のインスタンスを生成して返す
  return new Promise((resolve, reject) => {
    //乱数を使って、引数に指定された min と比較するための値を生成
    const rand = Math.floor( Math.random() * 1000);
    setTimeout(() => {
      //成功した場合(rand の値が引数の min 以上)
      if (rand >= min ) {
        //resolve() の引数に指定した値は、成功したときのコールバック(29行目)に渡される
        resolve(`${string} (rand: ${rand} >= min: ${min})`);
      } else {
        //失敗した場合(rand の値が引数の min 未満)
        //引数に指定したエラーオブジェクトは失敗した時のコールバック(31行目)に渡される
        reject(new Error(`失敗。(rand: ${rand} < min: ${min})`));
      }
    }, rand );
  });
}

//上記関数で Promise を生成し、その then() メソッドに成功時と失敗時の処理(関数)を登録
myPromise('Hello', 300).then(
  //処理が成功したときのコールバック(第1引数)
  (result) => console.log(result),  //受け取った結果を出力
  //処理が失敗したときのコールバック(第2引数)
  (error) => console.log(error.message)  //受け取ったエラーを出力
); 

catch() メソッド

catch() メソッドは promise オブジェクトが rejected の状態になった時(処理が失敗した場合)に呼ばれる関数を登録するためのメソッドです。

then() メソッドに1つだけ引数を指定して成功した場合のコールバック関数だけを登録し、失敗した場合のコールバック関数は catch() を使って登録することができます。

promise.then(
  //成功した場合(第1引数のみ指定)
  (value) => { /*成功した場合に実行する処理*/ }
).catch(
  //失敗した場合
  (error) => { /*失敗した場合に実行する処理*/ }
); 

前述のサンプルは以下のように書き換えることができます。

myPromise('Hello', 500).then(
  //成功した場合(第1引数のみ指定)
  (result) => console.log(result)
).catch(
  //失敗した場合の処理は catch() メソッド内に記述
  (error) => console.log(error.message)
);

Promise チェーン

then メソッドと catch メソッドは Promise インスタンスを返します。

そのため、then メソッドの返り値である Promise インスタンスにさらに then メソッドで処理を登録することで、これらをチェーン (連鎖) させることができます。

Promise チェーンでは、Promise の処理が失敗して rejected 状態にならない限り、順番に then メソッドで登録した成功時のコールバック関数が呼び出されます。

then メソッドのコールバック関数が返した値は、次の then メソッドのコールバック関数へ引数として渡されます。

以下は then メソッドを使った Promise チェーンの動作を確認する例です。

new Promise( (resolve) =>{
  setTimeout(() => resolve('hello #1'), 1000); //1秒で解決される
}).then( (result)=> {
  console.log(result); // hello #1
  return result + ' hello #2'; //結果を処理して返す
}).then( (result)=>{
  //5行目で return された値が result に入っている
  console.log(result); // hello #1 hello #2
  return result + ' hello #3'; //結果を処理して返す
}).then( (result)=> {
  //9行目で return された値が result に入っている
  console.log(result); //  hello #1 hello #2 hello #3
});

/*出力例
hello #1
hello #1 hello #2
hello #1 hello #2 hello #3
*/

上記の場合、最初の promise は1秒で解決され、最初の then メソッドが呼ばれ hello が出力されます。そして直ちに次のメソッドが呼ばれて hello hello2 が出力され、続いて hello hello2 hello3 が出力されます(実際には1秒後に順番通りにほぼ同時に出力されます)。

async / await

Async Function は非同期処理を扱う(promise を利用する)関数を定義する構文で await キーワードと組み合わせて使うと、Promise を簡潔に効率よく記述することができます。

Async Function は通常の関数とは異なり、async キーワードを関数の前に付けます。async を関数の前に付けることで、その関数は Promise オブジェクトを返すようになります。

await は Async Function の関数の中でのみ動作するキーワードで、指定した非同期関数の Promise が確定してその結果が返されるまで待ちます。そして Promise の処理が完了すると解決された値を返し、Async Function 内の処理を再開(次の行の処理へ移行)します。

Async Function と await を使うと、then() メソッドを使った記述よりも簡潔に記述できます。

以下は、前述の Promise の動作を確認するためのサンプルの then() メソッドの部分を Async Function と await を使って書き換えた例です。

//Promise を生成して返す関数(前述の例と同じ内容)
const myPromise = (string, min) => {
  //Promise のインスタンスを生成して返す
  return new Promise((resolve, reject) => {
    const rand = Math.floor( Math.random() * 1000);
    setTimeout(() => {
      if (rand >= min ) {
        resolve(`${string} (rand: ${rand} >= min: ${min})`);
      } else {
        reject(new Error(`失敗。(rand: ${rand} < min: ${min})`));
      }
    }, rand );
  });
}

//Async Function と await を使って then() メソッドの処理を書き換え
async function myAsync(string, min) {
  //await を指定→ myPromise() の処理が完了するまで待つ
  //result には Promise インスタンスの成功または失敗の結果の値が入る
  const result = await myPromise(string, min);

  //以下の行は await を指定した myPromise() の処理が完了を待って実行される
  console.log(result);
}

//上記で定義した Async Function を実行し、catch() メソッドでエラーを捕捉
myAsync('Hello', 500).catch(
  //非同期処理が失敗したときのコールバック(第2引数)
  (error) => console.log(error.message)  //エラーを表示
);

上記の Async Function はアロー関数を使って記述すると以下のようになります。

const myAsync = async (string, min) => {
  const result = await myPromise(string, min) ;
  console.log(result);
} 

また、エラーは以下のように try...catch 構文でキャッチすることもできます。

const myAsync = async (string, min) =>{
  // try...catch 構文
  try {
    //result には Promise インスタンスの成功または失敗の結果の値が入る
    const result = await myPromise(string, min) ;
    console.log(result);
  } catch (error) { // エラーを捕捉
    // 非同期処理が失敗し場合
    console.log(error.message);
  }
}
//上記で定義した Async Function を実行
myAsync('Hello', 500);