Fetch API
ブラウザで動くJavaScriptからHTTPリクエストを発行する
これまで、ブラウザがサーバーに対してリクエストを送信するのは、リンクがクリックされたときや、フォームが送信されたときなど、ページの再読み込みが起こる場合のみでした。
しかしながら、ブラウザ上で動くJavaScriptから利用できるFetch APIを用いると、任意のタイミングでリクエストが発行できるようになります。APIは、アプリケーションプログラミングインターフェース (Application Programming Interface) の略で、あるソフトウェアの機能や管理するデータを、外部の他のソフトウェアで利用するための手順やデータ形式を定めた規約のことです。多くのソフトウェアが共通して利用する機能がまとめて提供されており、APIに従い短いコードを記述するだけでその機能を利用することができます。
サーバーとクライアント、どちらで動くJavaScriptなのかに注意しながら、次のプログラムを実行してみましょう。
<button id="fetch-button">天気予報を見る</button>
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
メソッドは、レスポンスボディ全体を文字列として読み込むための非同期関数です。
なお、サーバーでは次のプログラムが動作しているものとします。
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
プロパティに指定します。
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);
};
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"
に指定します。
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
を用います。
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/html | HTML |
text/css | CSS |
text/javascript | JavaScript |
application/json | JSON |
image/jpg | JPEG |
image/png | PNG |
課題
Fetch API
を用いてチャットアプリを作成してみましょう。
ヒント
掲示板を作ったときと同じく、messages
という配列をサーバー側に用意し、メッセージが送信されたらその配列に要素を追加するようにしましょう。
const messages = [];
app.post("/send", (request, response) => {
// メッセージを追加
});
/messages
へのGETリクエストに対し、メッセージの一覧をJSONで応答するようにしてみましょう。
express.Response#json
メソッドは、受け取ったオブジェクトをJSON.stringify
によってJSONとしたうえでレスポンスするためのメソッドです。このとき、Content-Type
レスポンスヘッダは自動的に"application/json"
に設定されます。
app.get("/messages", (request, response) => {
response.json(messages);
});
新着メッセージを確認するために、定期的に/messages
に対してfetch
関数を用いてリクエストしましょう。setInterval
関数が利用できます。
setInterval(async () => {
const response = await fetch("/messages");
// レスポンスを処理する
}, 1000);
innerHTML
プロパティを空文字列とすることで要素の子要素を全て削除できます。document.createElement
関数を用いて再び生成し直しましょう。
<ul id="message-list"></ul>
const messageList = document.getElementById("message-list");
messageList.innerHTML = "";
for (const message of messages) {
const li = document.createElement("li");
li.textContent = message;
messageList.appendChild(li);
}
解答
解答は次のリンクを参照してください。