メインコンテンツまでスキップ

Fetch API

ブラウザで動く JavaScript から HTTP リクエストを発行する

これまで、ブラウザがサーバーに対してリクエストを送信するのは、リンクがクリックされたときや、フォームが送信されたときなど、ページの再読み込みが起こる場合のみでした。

しかしながら、ブラウザ上で動く JavaScript から利用できる Fetch API を用いると、任意のタイミングでリクエストが発行できるようになります。API は、アプリケーションプログラミングインターフェース(Application Programming Interface)の略で、あるソフトウェアの機能や管理するデータを、外部の他のソフトウェアで利用するための手順やデータ形式を定めた規約のことです。多くのソフトウェアが共通して利用する機能がまとめて提供されており、API に従い短いコードを記述するだけでその機能を利用することができます。

サーバークライアント、どちらで動く JavaScript なのかに注意しながら、次のプログラムを実行してみましょう。

static/index.html の body 内
<button id="fetch-button">天気予報を見る</button>
static/script.js (ブラウザ上で動く JavaScript)
document.getElementById("fetch-button").onclick = async () => {
const response = await fetch("/weather");
const weather = await response.text();
alert(weather);
};

async () => {} は、非同期関数、つまり async キーワードのついた関数を生成するためのアロー関数式です。

fetch 関数は、リクエストを発行するための関数です。標準ではGET リクエストが発行されます。この関数の戻り値に await 演算子 を適用すると、発行したリクエストに対する Response クラスのインスタンスが得られます。fetch 関数を利用することで、ページの再読み込みを伴わず、関数が実行されるタイミングでリクエストを発行することができます。

Response#text メソッドは、レスポンスボディ全体を文字列として読み込むための非同期関数です。

なお、サーバーでは次のプログラムが動作しているものとします。

main.mjs (サーバーとして動く JavaScript)
import express from "express";
const app = express();

app.use(express.static("static"));

app.get("/weather", (request, response) => {
response.send("晴れ");
});

app.listen(3000);

POST リクエストを送信する

何もオプションをつけずに呼び出された fetch 関数は、GET リクエストを送信します。しかしながら、fetch 関数の第 2 引数に指定したオブジェクトの method プロパティに "post" を指定することで、POST リクエストを送信できます。

このとき、リクエストボディは、 fetch 関数の第 2 引数に指定したオブジェクトの body プロパティに指定します。

static/script.js
document.getElementById("send-button").onclick = async () => {
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const body = new URLSearchParams({ name: name, age: age });
const response = await fetch("/send", { method: "post", body: body });
const text = await response.text();
alert(text);
};
main.mjs
import express from "express";
const app = express();

app.use(express.urlencoded({ extended: true }));
app.use(express.static("static"));

app.post("/send", (request, response) => {
response.send(
`あなたの名前は ${request.body.name}で、${request.body.age}歳ですね。`,
);
});

app.listen(3000);

HTML のフォームで送ったものと同じ形式でデータを送信するには、GET リクエストと POST リクエスト節で扱ったように、リクエストボディクエリ文字列の形式になっている必要があります。URLSearchParams クラスを用いると、クエリ文字列を簡単に扱うことができます。この例では、リクエストボディには name=入力された名前&age=入力された年齢 といった文字列が格納されます。

リクエストボディJSON を使用する

前項では、リクエストボディクエリ文字列の形式を用いましたが、JSON を用いることで、より複雑なデータを扱えるようになります。

JSON.stringify 関数は、JavaScript オブジェクトを受け取って JSON 文字列を返す関数です。この値をリクエストボディに指定しています。

fetch 関数の第 2 引数の headers オプションでは、リクエストヘッダを指定します。リクエストボディJSON を指定する場合は、Content-Type リクエストヘッダ"application/json" に指定します。

static/script.js
document.getElementById("send-button").onclick = async () => {
const name = document.getElementById("name").value;
const age = document.getElementById("age").value;
const json = JSON.stringify({ name: name, age: age });
const response = await fetch("/send", {
method: "post",
headers: { "Content-Type": "application/json" },
body: json,
});
const text = await response.text();
alert(text);
};

サーバー側では、リクエストボディの JSON を解釈するため、express.urlencoded の代わりに express.json を用います。

main.mjs
import express from "express";
const app = express();

app.use(express.json());
app.use(express.static("static"));

app.post("/send", (request, response) => {
response.send(
`あなたの名前は ${request.body.name}で、${request.body.age}歳ですね。`,
);
});

app.listen(3000);
Content-Type リクエスト・レスポンスヘッダ

Content-Type ヘッダは、リクエストボディレスポンスボディの種類を識別するために使用されます。ここで使用する種類は、MIME タイプと呼ばれます。

代表的な MIME タイプとして、次のような値が定義されています。

MIME タイプ種類
text/htmlHTML
text/cssCSS
text/javascriptJavaScript
application/jsonJSON
image/jpgJPEG
image/pngPNG

課題

Fetch API を用いてチャットアプリを作成してみましょう。

ヒント

掲示板を作ったとき と同じく、messages という配列をサーバー側に用意し、メッセージが送信されたらその配列に要素を追加するようにしましょう。

main.mjs
const messages = [];
app.post("/send", (request, response) => {
// メッセージを追加
});

/messages への GET リクエストに対し、メッセージの一覧を JSON で応答するようにしてみましょう。

express.Response#json メソッドは、受け取ったオブジェクトを JSON.stringify によって JSON としたうえでレスポンスするためのメソッドです。このとき、Content-Type レスポンスヘッダは自動的に "application/json" に設定されます。

main.mjs
app.get("/messages", (request, response) => {
response.json(messages);
});

新着メッセージを確認するために、定期的に /messages に対して fetch 関数を用いてリクエストしましょう。setInterval 関数が利用できます。

static/script.js
setInterval(async () => {
const response = await fetch("/messages");
// レスポンスを処理する
}, 1000);

innerHTML プロパティを空文字列とすることで要素の子要素を全て削除できます。document.createElement 関数を用いて再び生成し直しましょう。

static/index.html
<ul id="message-list"></ul>
static/script.js
const messageList = document.getElementById("message-list");
messageList.innerHTML = "";

for (const message of messages) {
const li = document.createElement("li");
li.textContent = message;
messageList.appendChild(li);
}

解答

解答は次のリンクを参照してください。