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

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);
}

解答

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