JavaScriptの応用構文
この章では、JavaScriptを使ってHTMLを動的に操作する応用構文をハンズオン形式で学習しながら実装します。これにより、DOM操作と非同期処理を組み合わせて、動くWebページを実装する力が身につきます。
1. 本章の概要
1.1 本章の目的
前章 JavaScriptの基本文法 で扱った言語仕様だけでは、ユーザ操作や外部通信をきっかけに画面が動くWebアプリケーションは作れません。本章では、DOM操作とAPI呼び出し(Promise / async / await)といった、素のJavaScriptで画面を動かすための応用構文を扱います。ここで押さえた書き方は、次章の問い合わせフォームでそのまま組み合わせて使い、後続のReact / Next.jsを扱う章では「フレームワークが裏で何を肩代わりしているのか」を理解する土台になります。
1.2 ハンズオンの流れ
1つのフォルダにHTMLファイルとJavaScriptファイルを用意し、JavaScriptファイルの中身を段階的に書き換えながら進めます。最初に document オブジェクトを通じて画面上の要素を書き換える方法を扱い、次に fetch 関数で外部サービスと通信しながら、Promise・async / await・try / catch・Promise.allといった非同期処理の書き方を1つずつ動かします。ここで扱う書き方は、次章の問い合わせフォームの実装にそのまま活用します。
1.3 事前準備
必要なツール
この章では、以下のツールを使用します。まだインストールしていない場合は、リンク先の手順に沿って準備をお願いします。
| ツール名 | 関連箇所 | 理由 |
|---|---|---|
| Google Chrome(またはFirefox / Edge / Safari) | – | 作成したページを開き、DevToolsのConsoleで動作を確認する |
| Visual Studio Code | Visual Studio Codeのインストール | index.html と app.js を作成・編集するエディタとして使用する |
2. HTMLからの呼び出し
JavaScriptは単独で動くわけではなく、ブラウザに読み込まれたHTMLの一部として動作することができます。ブラウザに「このHTMLと一緒に、このJavaScriptも実行してほしい」と伝えるための仕組みが <script> タグです。本章では、まずHTMLに外部JavaScriptファイルを読み込む書き方と、読み込みタイミングを制御する defer 属性の役割を確認します。
<script> タグと defer 属性
外部 JavaScript ファイルを HTML に読み込むときは、<script> タグの src 属性でファイル名を指定します。あわせて defer 属性を付けると、ブラウザに「スクリプトを実行するタイミング」を指示できます。書き方は以下のとおりです。
<script src="ファイル名" defer></script>
このセクションで実際に <script defer> を書いて動作を確認します。まず、以降のハンズオン全体で使う作業用フォルダを用意します。任意の場所に frontend-js-advanced フォルダを作成し、その中に index.html と app.js の 2 ファイルを作成します。
frontend-js-advanced/
├── index.html ← このファイルを作成
└── app.js ← このファイルを作成
作成した index.html に以下の内容を記述して保存します。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>JavaScriptの応用構文</title>
</head>
<body>
<h1>こんにちは</h1>
<script src="app.js" defer></script>
</body>
</html>
app.js には以下の内容を記述して保存します。
alert("app.js が読み込まれました");
Finder またはエクスプローラーから index.html をダブルクリックしてブラウザで開きます。ページには「こんにちは」の見出しが表示されると同時に、以下のようなポップアップがブラウザ上に表示されます。

Chrome では「このページの内容」というタイトルの下に、alert() に渡した「app.js が読み込まれました」のメッセージが表示されます。「OK」を押すとポップアップが閉じ、通常の画面表示に戻ります。ポップアップが表示されたことで、index.html から app.js が読み込まれて alert() が実行されたことを確認できます。
コードを解説します。
<script src="app.js" defer></script>
src 属性で読み込む JavaScript ファイル名を指定します。相対パスで書けば、同じフォルダにある app.js を読み込みます。defer を付けると、ブラウザに「HTML をすべて読み込んでからスクリプトを実行する」よう指示できます。これにより、スクリプトから <body> 内の <h1> などの要素を安全に参照できます。
defer を付けずに <head> 内で <script> を書くと、ブラウザは HTML の解析を途中で止めてスクリプトのダウンロードと実行を待ちます。この間は画面に何も表示されないため、スクリプトのファイルサイズが大きいほど「白い画面のまま反応がない」時間が長くなります。defer を付けておけば HTML の解析とスクリプトのダウンロードが並行で進み、この待ち時間が発生しません。
alert("app.js が読み込まれました");
alert() は、引数の文字列をブラウザのポップアップとして表示するグローバル関数です。ブラウザが標準で備えているため、追加のライブラリなしで使えます。ポップアップが表示されている間はページ上の他の操作がブロックされ、「OK」を押すまで JavaScript の後続処理も止まります。動作結果を最小限のコードで即座に確認できるため、本章では動作確認の出力手段として基本的に alert() を使います。
| 💡 ポイント |
|---|
defer の代わりに <script> を <body> の末尾に置いても、HTML の読み込みが完了した後に実行されるという結果は同じです。現代の Web 開発では、置き場所に依存せず動作するように defer を付ける書き方が広く使われます。 |
以降のセクションでは、この app.js の中身を書き換えながら、DOM 操作や API 呼び出しを試します。
3. DOM操作
JavaScriptから画面上の要素を取得したり書き換えたりするには、HTMLをJavaScriptから扱うためのオブジェクト構造である DOM(Document Object Model)を使います。ブラウザはHTMLを読み込むとDOMツリーを構築し、JavaScriptは document オブジェクトを通じてこのツリーへアクセスします。本セクションでは、要素の取得・変更・追加・削除・スタイル変更・イベント処理といったDOM操作の基本を、1つずつ順に扱います。
3.1 動作確認用HTMLの準備
以降のサンプルで扱う要素をまとめて用意するため、index.html を以下の内容で上書き保存します。見出しに加えて、要素の取得やクリック操作を試すための <button>、複数要素の取得を試す <ul>、スタイルを変更する対象の <div> を追加しています。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8" />
<title>JavaScriptの応用構文</title>
</head>
<body>
<h1>こんにちは</h1>
<button id="btn">クリック</button>
<ul id="list">
<li>りんご</li>
<li>みかん</li>
<li>ぶどう</li>
</ul>
<div id="box">ボックス</div>
<script src="app.js" defer></script>
</body>
</html>
保存してブラウザで index.html を再読み込みすると、以下のような画面が表示されます。

見出し「こんにちは」・ボタン「クリック」・箇条書き(りんご / みかん / ぶどう)・ブロック「ボックス」の 4 つの要素が上から順に表示されます。CSS を当てていないため、それぞれの要素はブラウザ標準の見た目で描画されています。
以降のセクションでは、この index.html を出発点として、app.js の中身を段階的に書き換えながらDOM操作を試します。
3.2 要素の取得
DOM操作は、まず「対象の要素をJavaScriptで取得する」ところから始まります。取得したい要素が1つか複数かに応じて、document.querySelector と document.querySelectorAll を使い分けます。
単一要素の取得
HTMLの要素をJavaScriptから取得する方法はいくつか用意されていますが、本章では CSSセレクタと同じ書き方で要素を指定できる document.querySelector を中心に扱います。前章 HTML/CSSの基本 で扱ったCSSセレクタをそのまま流用でき、ID指定・クラス指定・タグ指定・複合セレクタなどを1つのメソッドで扱えるため、記法を統一できるのが利点です。書き方は以下のとおりです。
const 変数 = document.querySelector("CSSセレクタ");
CSSセレクタの部分には、CSSと同じ書き方がそのまま使えます。代表的な指定方法は以下のとおりです。
| 種類 | 書き方 | 例 |
|---|---|---|
| ID指定 | #ID名 |
#header で id="header" の要素を取得する |
| クラス指定 | .クラス名 |
.card で class に card を含む要素を取得する |
| タグ指定 | タグ名 |
p で <p> 要素を取得する |
| 子孫指定 | 親セレクタ 子セレクタ |
#list li で <ul id="list"> の中の <li> を取得する |
| 属性指定 | タグ名[属性名="値"] |
input[type="text"] で type="text" の <input> を取得する |
マッチする要素が1つも見つからない場合は null が返ります。
これは、<h1> 要素と <button id="btn"> 要素を取得して、それぞれをポップアップで表示するコードです。app.js を以下の内容で上書き保存します。
const heading = document.querySelector("h1");
const button = document.querySelector("#btn");
alert(heading.outerHTML + "\n" + button.outerHTML);
ブラウザで index.html を再読み込み(Command + R または Ctrl + R)すると、以下のようなポップアップが表示されます。

「このページの内容」のタイトルの下に、<h1>こんにちは</h1> と <button id="btn">クリック</button> の 2 つの要素が改行区切りで並んで表示されました。h1 と #btn のセレクタで、それぞれ <h1> 要素と <button> 要素を取得できています。取得した要素の outerHTML プロパティを参照すると、その要素の HTML 表現を文字列として取り出せます。
コードを解説します。
const heading = document.querySelector("h1");
document.querySelector("h1") は <h1> タグの要素を1つ取得します。h1 は要素名そのままなので、# や . を付けずに書きます。
const button = document.querySelector("#btn");
#btn はIDが btn の要素を指すCSSセレクタです。IDはページ内で一意に振る前提のため、document.querySelector で取得すると <button id="btn"> 要素が返ります。
なお、DOM要素を取得するメソッドは document.querySelector 以外にもいくつか用意されています。以下は代表的なものです。
| メソッド | 用途 |
|---|---|
document.querySelector("CSSセレクタ") |
最初にマッチした1つの要素を返す |
document.querySelectorAll("CSSセレクタ") |
マッチしたすべての要素を NodeList で返す |
document.getElementById("id") |
ID指定で1つの要素を返す。querySelector("#id") と同じ結果になる |
document.getElementsByClassName("class") |
クラス指定で複数要素を HTMLCollection で返す |
document.getElementsByTagName("tag") |
タグ指定で複数要素を HTMLCollection で返す |
getElementById / getElementsByClassName / getElementsByTagName は querySelector が普及する前から用意されているメソッドで、既存のコードでもよく見かけます。新規に書く場合は、CSSセレクタで統一的に指定できる querySelector / querySelectorAll を使えば十分です。
複数要素の取得
document.querySelector は最初にマッチした1つの要素だけを返します。しかし、リストの各項目や同じクラスを持つカードなど、複数の要素をまとめて取得したいケースもあります。その場合は document.querySelectorAll を使います。書き方は以下のとおりです。
const 変数 = document.querySelectorAll("CSSセレクタ");
返り値は、マッチしたすべての要素を含む NodeList と呼ばれるコレクションです。配列に似ていて、forEach メソッドで各要素を順番に処理できます。
これは、<ul id="list"> の中のすべての <li> 要素を取得し、forEach で各要素のテキストを集めてポップアップで表示するコードです。app.js を以下の内容で上書き保存します。
const items = document.querySelectorAll("#list li");
const texts = [];
items.forEach((item) => {
texts.push(item.textContent);
});
alert(texts.join("\n"));
ブラウザで index.html を再読み込みすると、以下のようなポップアップが表示されます。

「このページの内容」のタイトルの下に、りんご / みかん / ぶどう の 3 つの <li> の中身が改行区切りで並んで表示されました。3 件すべての中身が並んでいることが動作確認のポイントで、querySelectorAll が最初の 1 件だけでなくマッチする全要素を返せていること、forEach で各要素を順に取り出せていることの両方が確認できます。
コードを解説します。
const items = document.querySelectorAll("#list li");
#list li は「IDが list の要素の中にある <li> 要素すべて」を指すセレクタです。querySelectorAll はマッチした要素を NodeList にまとめて返します。
const texts = [];
items.forEach((item) => {
texts.push(item.textContent);
});
alert(texts.join("\n"));
NodeList は forEach メソッドを持っており、渡したコールバック関数を要素の数だけ呼び出します。forEach の中で item.textContent を配列 texts に集めていき、最後に join("\n") で改行区切りの1つの文字列にまとめて alert() で表示しています。ポップアップが3回連続で出るのを避けるため、集約してから1回だけ出力する形にしています。
3.3 イベントリスナ
要素を取得できるようになりましたが、取得しただけでは app.js が実行された瞬間の1回きりの処理しか書けません。ユーザ操作(ボタンのクリックやフォーム入力など)をきっかけに何度も処理を動かすには、イベント の発生を待ち受ける イベントリスナ を使います。イベントリスナは addEventListener メソッドで登録します。書き方は以下のとおりです。
要素.addEventListener("イベント名", (event) => {
// イベント発生時に実行する処理
});
第1引数にはイベント名("click" / "input" / "submit" など)、第2引数にはイベントが発生したときに呼ばれるコールバック関数を渡します。同じ要素に対して複数のリスナを登録することもできます。
これは、ボタンをクリックするとポップアップを表示するだけのコードです。app.js を以下の内容で上書き保存します。
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
alert("クリックされました");
});
ブラウザで index.html を再読み込みし、「クリック」ボタンを押すと、以下のようなポップアップが表示されます。

「このページの内容」のタイトルの下に「クリックされました」のメッセージが表示されます。ボタンを押すたびに毎回このポップアップが出ることが動作確認のポイントで、addEventListener("click", ...) に渡したコールバック関数が、クリックイベントの発生をきっかけに実行されていることが確認できます。以降のセクションでは、この alert() の部分を実際のDOM操作(テキストの書き換え・スタイルの切り替え・要素の追加)に差し替える形で、クリックをきっかけに画面が動く例を扱います。
コードを解説します。
const button = document.querySelector("#btn");
前のセクションで扱った document.querySelector で、IDが btn の <button> 要素を取得しています。
button.addEventListener("click", () => {
alert("クリックされました");
});
addEventListener の第1引数に "click" を、第2引数にアロー関数のコールバックを渡します。ここではコールバックの中で alert() を呼ぶだけの単純な処理ですが、ボタンをクリックするたびにこのコールバックが呼ばれる、という骨格は以降のすべての例で共通です。
3.4 要素の変更
取得した要素に対しては、中身の文字列を書き換える・クラスやスタイルを付ける・新しい要素を追加する・要素を削除する、といった変更を加えたい場面が出てきます。ここからは、前セクションで扱ったイベントリスナと組み合わせて、代表的な変更手段を1つずつ扱います。
テキストの書き換え
前のセクションで登録したイベントリスナの中で alert() を呼ぶ代わりに、実際に画面上の要素を書き換えてみます。要素の中身を書き換えるには、textContent プロパティに新しい文字列を代入します。書き方は以下のとおりです。
要素.textContent = "新しい文字列";
textContent は要素の中に表示されている文字列を表すプロパティで、値の読み出しにも書き込みにも使えます。代入した瞬間にDOMツリーが書き換わり、ブラウザの表示も同時に更新されます。
これは、ボタンをクリックしたら <h1> の中身を「こんばんは」に書き換えるコードです。app.js を以下の内容で上書き保存します。
const heading = document.querySelector("h1");
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
heading.textContent = "こんばんは";
});
ブラウザで index.html を再読み込みすると、最初は「こんにちは」の見出しが表示されます。「クリック」ボタンを押すと、以下のように見出しの文字が「こんばんは」に切り替わります。

ボタンを押す前と後で見出しの文字が変わるのがポイントで、textContent に代入するだけでDOMツリーとブラウザ表示が同時に書き換わることが確認できます。index.html のソースは <h1>こんにちは</h1> のまま変わっていないため、DOM側の状態がそのまま画面に反映されていることも合わせて分かります。
コードを解説します。
button.addEventListener("click", () => {
heading.textContent = "こんばんは";
});
前セクションで扱った addEventListener の骨格の中で、alert() の代わりに heading.textContent への代入を書きました。テンプレートリテラルと組み合わせれば、変数の値を埋め込んだ動的な文字列を代入することもできます。textContent は要素の中の文字列を書き換えるだけのプロパティですが、要素の変更手段はほかにも多数あります。以降のセクションで代表的なものを扱い、章末で全体像をまとめます。
クラスの操作
テキストの書き換えを扱ってきましたが、ほかにも要素の見た目そのものを変える方法があります。CSS側にクラスごとのスタイルを定義しておき、JavaScriptからは classList プロパティ経由でクラスの付け外しだけを行う書き方が一般的です。書き方は以下のとおりです。
要素.classList.add("クラス名"); // クラスを追加する
要素.classList.remove("クラス名"); // クラスを削除する
要素.classList.toggle("クラス名"); // クラスがなければ追加し、あれば削除する
これは、ボタンをクリックするたびに <div id="box"> の highlight クラスを付け外しし、そのつどの class 属性の状態をポップアップで表示するコードです。app.js を以下の内容で上書き保存します。
const box = document.querySelector("#box");
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
box.classList.toggle("highlight");
alert("現在のクラス: " + (box.className || "(クラスなし)"));
});
ブラウザで index.html を再読み込みし、「クリック」ボタンを1回押すと、以下のようなポップアップが表示されます。

「このページの内容」のタイトルの下に「現在のクラス: highlight」のメッセージが表示されます。もう1度「クリック」を押すと、highlight クラスが取り除かれて以下の表示に変わります。
現在のクラス: (クラスなし)
クリックのたびに highlight の有無が反転することが動作確認のポイントで、classList.toggle が「クラスがあれば削除・なければ追加」を1つのメソッドで担っていることが確認できます。ボタンのアクティブ表示のオン/オフや、モーダルの表示切り替えなど、状態を反転させたい場面で便利です。
コードを解説します。
box.classList.toggle("highlight");
classList.toggle はクラスがあれば削除、なければ追加します。同じ処理を add と remove で書き分ける必要がなくなります。
alert("現在のクラス: " + (box.className || "(クラスなし)"));
className プロパティは、要素の class 属性の中身を空白区切りの文字列として返します。クラスが1つもない場合は空文字列 "" になるため、|| 演算子で (クラスなし) に置き換えて表示しています。
add / remove は「開くタイミングで open クラスを付ける/閉じるタイミングで外す」のように、既にある状態からクラスを追加・削除するだけの用途で使い分けます。
スタイルの操作
クラスを介さず、JavaScriptから直接インラインスタイルを書き換えたい場合は、style プロパティを使います。書き方は以下のとおりです。
要素.style.プロパティ名 = "値";
CSSのプロパティ名 background-color は、JavaScript側では backgroundColor のようにキャメルケースで書きます。
これは、ボタンをクリックすると <div id="box"> に背景色・文字色・余白のインラインスタイルを付けるコードです。app.js を以下の内容で上書き保存します。
const box = document.querySelector("#box");
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
box.style.backgroundColor = "lightyellow";
box.style.color = "darkblue";
box.style.padding = "12px";
});
ブラウザで index.html を再読み込みすると、最初は「ボックス」と書かれた <div> がブラウザ既定のスタイルで表示されます。「クリック」ボタンを押すと、<div> の見た目が以下のように変わります。

「ボックス」の <div> に淡い黄色の背景色・濃い青の文字色・上下左右12pxの余白が付いた状態に切り替わりました。HTML 側の <div id="box"> には何のスタイルも書いていないにもかかわらず、クリック後に 3 つのスタイルが画面に反映されていれば、style プロパティ経由でインラインスタイルを書き換えられたことが動作確認できます。
コードを解説します。
box.style.backgroundColor = "lightyellow";
box.style.color = "darkblue";
box.style.padding = "12px";
style プロパティ経由でインラインスタイルを設定しています。ブラウザで要素を検証すると、<div id="box" style="background-color: lightyellow; color: darkblue; padding: 12px;"> のように style 属性が追加されているのが確認できます。
| 💡 ポイント |
|---|
インラインスタイルはJavaScriptから直接値を書けて手軽ですが、見た目の定義がCSSファイルと app.js に分散するのが難点です。CSS側に .highlight { background-color: lightyellow; } のようなクラスを定義しておき、JavaScriptからは box.classList.add("highlight") のようにクラスの付け外しだけを行う書き方が一般的です。見た目の定義がCSSに集約され、JavaScriptは「どのタイミングでどのクラスを切り替えるか」に専念できます。 |
要素の追加・削除
ここまでは既にHTMLに書かれている要素の中身や見た目を書き換えてきましたが、新しい要素を作って追加したり、既存の要素を削除したりする操作もあります。新しい要素は document.createElement で作り、親要素の appendChild メソッドでDOMツリーに追加します。書き方は以下のとおりです。
const 変数 = document.createElement("タグ名"); // 新しい要素を作る
親要素.appendChild(変数); // 親要素の末尾に追加する
要素.remove(); // 要素をDOMから削除する
これは、ボタンをクリックするたびに <ul id="list"> の末尾に新しい <li> を追加するコードです。app.js を以下の内容で上書き保存します。
const list = document.querySelector("#list");
const button = document.querySelector("#btn");
let count = 0;
button.addEventListener("click", () => {
count++;
const newItem = document.createElement("li");
newItem.textContent = `新しい項目 ${count}`;
list.appendChild(newItem);
});
ブラウザで index.html を再読み込みすると、リストには最初「りんご/みかん/ぶどう」の 3 項目だけが表示されています。「クリック」ボタンを 1 回押すと、以下のようにリストが変化します。

リストの末尾に「新しい項目 1」が追加され、4 項目になりました。もう 1 回「クリック」を押すと、さらに末尾に項目が追加されます。

「新しい項目 2」が追加され、5 項目のリストになりました。クリックの回数だけリストの末尾に項目が増えていくことが動作確認のポイントで、createElement で作った要素も、appendChild でDOMツリーに追加した時点で画面表示に反映されることが確認できます。
コードを解説します。
const newItem = document.createElement("li");
newItem.textContent = `新しい項目 ${count}`;
document.createElement("li") は、まだDOMツリーには属していない <li> 要素を新しく作ります。この段階では画面には表示されず、textContent などのプロパティは通常の要素と同じように操作できます。
list.appendChild(newItem);
親要素の appendChild を呼ぶと、渡した要素が親要素の末尾に追加されます。この時点で初めてDOMツリーに組み込まれ、画面へ表示されます。
削除するときは、対象の要素に対して remove メソッドを呼び出します。例えば、追加した項目をすべて取り除きたい場合は、document.querySelectorAll で全 <li> を取得し、forEach で各要素の remove() を呼ぶ、といった書き方ができます。
document.querySelectorAll("#list li").forEach((item) => item.remove());
#list li:first-child のようにCSSの疑似クラスと組み合わせれば、「先頭の項目だけを削除」といった特定要素の削除もできます。
ここまでで、DOM操作の基本形「要素を取得する → イベントを登録する → 中身を書き換える/追加する/削除する」を一通り体験できました。以降のセクションでは、この基本を土台にして、外部サービスとの通信やフォームからの送信を組み合わせます。
3.5 DOM操作で使える主なプロパティ・メソッド
本セクションで扱った書き方は、DOM操作の中でよく使う代表例です。要素の変更・属性の操作・親子要素のたどり方など、他にも多数のプロパティ・メソッドが用意されています。代表的なものは以下のとおりです。
| 分類 | プロパティ / メソッド | 用途 |
|---|---|---|
| 要素の変更 | textContent |
要素の中の文字列を書き換える |
| 要素の変更 | innerHTML |
要素の中身をHTML文字列で書き換える |
| 要素の変更 | value |
<input> や <textarea> の入力値を書き換える |
| 要素の変更 | hidden |
true を代入すると要素を非表示にする |
| 要素の変更 | disabled |
true を代入すると <button> や <input> を操作不能にする |
| 属性の操作 | setAttribute("属性名", "値") |
href や src など任意の属性を書き換える |
| 属性の操作 | getAttribute("属性名") |
任意の属性の値を読み出す |
| クラス・スタイル | classList.add / remove / toggle |
クラスの追加・削除・切り替えを行う |
| クラス・スタイル | style.プロパティ名 |
インラインスタイル(背景色・幅・高さなど)を書き換える |
| 要素の追加・削除 | document.createElement("タグ名") |
新しい要素を作る |
| 要素の追加・削除 | 親要素.appendChild(要素) |
親要素の末尾に要素を追加する |
| 要素の追加・削除 | 要素.remove() |
要素をDOMから取り除く |
| 親子要素のたどり方 | parentElement |
親要素を取得する |
| 親子要素のたどり方 | children |
子要素のコレクションを取得する |
要素が持つプロパティ・メソッドはこの他にも多数あります。網羅的なリファレンスは、Element(MDN Web Docs) に記載があります。
| 💡 ポイント |
|---|
| 素のJavaScriptでDOM操作を書き続けると、要素の追加・削除・状態管理が絡み合ってコードが複雑になりやすいです。この講座の後半で扱うReactは、状態の変化に応じて画面を自動更新してくれるため、DOMを直接書き換える場面が大きく減ります。ここでDOMの仕組みを体験しておくと、Reactが裏で何を肩代わりしているのかを理解しやすくなります。 |
4. API呼び出し
Webアプリケーションでは、外部サービスと通信してデータをやり取りする場面が頻繁に出てきます。ブラウザから他のサーバへHTTPリクエストを送る仕組みが fetch API です。通信には時間がかかるため、他の処理を止めずに結果を待つ 非同期処理 の考え方も一緒に必要になります。本セクションでは、fetch の基本形からPromise・async / await・try / catch・Promise.allまでを、httpbin.org の各エンドポイントを呼び出しながら順に扱います。
| 📝 httpbin.orgとは |
|---|
| httpbin.org は、HTTPリクエストの動作確認を目的として公開されている無料のテスト用サービスです。GET・POST・遅延レスポンス・エラー応答など、さまざまなエンドポイントを提供しています。本章では、本物のバックエンドAPIの代役として、通信の動作を試すために利用します(このように、本物の代わりに使う仮のAPIを スタブ と呼びます)。 |
4.1 同期処理と非同期処理
コードの実行方式には 同期処理 と 非同期処理 の2種類があります。同期処理は、1つの処理が終わってから次の処理に進む方式です。ファイル読み込みやAPI通信のように時間のかかる処理を同期で行うと、その間ブラウザは他の操作を受け付けられなくなり、画面が固まってしまいます。
非同期処理 は、時間のかかる処理を裏で走らせつつ、他の処理を並行して進められる仕組みです。JavaScriptには、非同期処理の完了を表す Promise(プロミス)という仕組みが標準で用意されており、fetch などブラウザ組み込みの通信APIはすべてPromiseを返します。
本セクションの以降では、この Promise を fetch を題材に扱いながら、非同期処理を中心に、基本の書き方から、より簡潔な書き換え、エラーの捕捉、複数通信の並行実行まで、順に試します。
4.2 fetchによるデータ取得
外部サーバへHTTPリクエストを送り、レスポンスを受け取るときは fetch 関数を使います。書き方は以下のとおりです。
fetch("URL");
fetch の戻り値は「HTTPレスポンス」を表すPromiseです。Promiseは「今はまだ結果が確定していないが、いずれ確定する値」を表すオブジェクトで、成功時と失敗時の処理を .then() / .catch() のチェーンで書けます。書き方は以下のとおりです。
Promiseを返す関数().then((値) => 成功時の処理).catch((エラー) => 失敗時の処理);
これは、httpbin.org/uuid から一意なUUID文字列を取得し、ポップアップで表示するコードです。app.js を以下の内容で上書き保存します。
const button = document.querySelector("#btn");
button.addEventListener("click", () => {
fetch("https://httpbin.org/uuid")
.then((response) => response.json())
.then((data) => alert(JSON.stringify(data, null, 2)))
.catch((error) => alert("エラー: " + error.message));
});
ブラウザで index.html を再読み込みし、「クリック」ボタンを押すと、以下のようなポップアップが表示されます(uuid の値は毎回変わります)。

「このページの内容」のタイトルの下に、uuid プロパティを含む JSON がインデント付きで表示されました。ボタンを押すたびに毎回異なる uuid の値が返ってきていることが動作確認のポイントで、httpbin.org/uuid への通信が成立し、.then のチェーンで レスポンス取得 → JSON への変換 → 表示 と順に非同期処理をつなげられていることが確認できます。
コードを解説します。
fetch("https://httpbin.org/uuid")
fetch が返すPromiseは、Response オブジェクトへ解決されます。Response はHTTPステータスやレスポンスヘッダなどを含むオブジェクトで、実際のボディはこの段階ではまだ読み出せていません。
.then((response) => response.json())
response.json() はレスポンスボディをJSONとして解釈する関数で、こちらもPromiseを返します。.then の中でPromiseを返すと、次の .then はそのPromiseの解決を待って呼ばれます。
.then((data) => alert(JSON.stringify(data, null, 2)))
JSONから変換されたJavaScriptオブジェクトを、JSON.stringify() の第3引数 2 でインデント2スペースの整形付き文字列に変換してポップアップ表示しています。alert() はオブジェクトを直接渡すと [object Object] としか表示されないため、JSON.stringify() で文字列化してから渡します。
.catch((error) => alert("エラー: " + error.message));
途中のどこかでPromiseが失敗(reject)になると、.catch に登録した関数がエラーオブジェクトを受け取って呼ばれます。ネットワーク切断など、通信そのものが失敗したときの受け皿になります。
4.3 async / awaitへの書き換え
.then のチェーンでも動作は問題ありませんが、通信を複数回連続で行う場合はネストが深くなり読みづらくなります。同じ非同期処理を、同期処理と同じ見た目で書ける仕組みが async / await です。書き方は以下のとおりです。
const 関数名 = async () => {
const 値 = await Promiseを返す関数();
// 値を使った処理
};
async を付けた関数の中では await が使えます。await は「Promiseが解決するまで一時停止し、解決した値を返す」動作をします。書き換えると、.then のコールバックが「変数への代入」と同じ見た目になります。
これは、先ほどの fetch の例をasync / awaitで書き換えたコードです。app.js を以下の内容で上書き保存します。
const button = document.querySelector("#btn");
const fetchUuid = async () => {
const response = await fetch("https://httpbin.org/uuid");
const data = await response.json();
alert(JSON.stringify(data, null, 2));
};
button.addEventListener("click", fetchUuid);
ブラウザで index.html を再読み込みし、「クリック」ボタンを押すと、.then チェーン版と同じく以下のようなポップアップが表示されます。

「このページの内容」のタイトルの下に、uuid プロパティを含む JSON がインデント付きで表示されました。.then チェーン版と同じ結果が得られていることが動作確認のポイントで、.then を 1 つも書かずに、上から順に読める形で通信結果を受け取れています。処理内容は同じでも、フローの見通しが大きく改善されていることが分かります。
コードを解説します。
const fetchUuid = async () => {
async を関数に付けると、その関数の戻り値は自動的にPromiseで包まれ、内部で await が使えるようになります。
const response = await fetch("https://httpbin.org/uuid");
const data = await response.json();
await を付けると、Promiseの解決を待ってから解決値を受け取れます。上の2行は .then((response) => response.json()).then((data) => ...) と同じ意味ですが、書き方は変数への代入と同じ形になっています。
4.4 try / catchでのエラー捕捉
JavaScriptには、実行中に発生したエラーを捕まえて処理を続けるための try / catch という構文が用意されています。try ブロックの中で発生したエラーは、続く catch ブロックに引き渡され、そこで受け取って処理できます。async / awaitで書いた非同期処理のエラーも、この try / catch で捕捉できます。書き方は以下のとおりです。
try {
await Promiseを返す関数();
} catch (error) {
// エラー時の処理
}
await の対象がrejectされた場合や、await 中に例外が投げられた場合は、すべて catch ブロックに流れます。エラーオブジェクトの message プロパティで、失敗の内容を文字列として取り出せます。
これは、存在しないURLへアクセスしてエラーを発生させ、try / catch で捕捉するコードです。app.js を以下の内容で上書き保存します。
const button = document.querySelector("#btn");
const fetchInvalid = async () => {
try {
const response = await fetch("https://invalid.example.invalid/data");
const data = await response.json();
alert(JSON.stringify(data, null, 2));
} catch (error) {
alert("エラー: " + error.message);
}
};
button.addEventListener("click", fetchInvalid);
ブラウザで index.html を再読み込みし、「クリック」ボタンを押すと、以下のようなポップアップが表示されます(メッセージの文言はブラウザによって多少異なります)。

「このページの内容」のタイトルの下に「エラー: Failed to fetch」のメッセージが表示されました。存在しないドメインへの通信が失敗しても JavaScript が停止せず、catch ブロックのエラーメッセージがポップアップに出ていることが動作確認のポイントで、try / catch で囲むことで await 中の失敗を1箇所にまとめて処理できることが確認できます。
| ⚠️ 「エラー: Failed to fetch」が表示されず、CORSエラーになる場合 |
|---|
ブラウザによっては、DevToolsのConsoleに Access to fetch at ... has been blocked by CORS policy のようなCORSエラーが先に表示される場合があります。CORSエラーの結果として fetch のPromiseがrejectされるため、catch ブロックは同じように呼ばれます。表示されるメッセージが違っていても、動作としては同じです。 |
コードを解説します。
try {
const response = await fetch("https://invalid.example.invalid/data");
const data = await response.json();
alert(JSON.stringify(data, null, 2));
} catch (error) {
alert("エラー: " + error.message);
}
try ブロックには通常の処理を書き、await の対象がrejectされたときや、途中で例外が投げられたときに catch ブロックへ制御が移ります。Error オブジェクトの message プロパティに、失敗の内容が文字列として入っています。
4.5 Promise.allによる並行実行
複数の非同期処理を 並行に 実行し、すべての完了を待ちたい場合は Promise.all を使います。書き方は以下のとおりです。
const 結果配列 = await Promise.all([Promise1, Promise2, Promise3]);
Promise.all は渡された配列の要素をすべて並行に開始し、全部が解決するのを待って結果の配列を返します。1つでも失敗するとPromise全体が失敗扱いになる点は、後述の補足で扱います。
これは、httpbin.org/delay/1(1秒後にレスポンスを返すエンドポイント)を3つ並行に呼び出し、実行時間を測定するコードです。app.js を以下の内容で上書き保存します。
const button = document.querySelector("#btn");
const fetchAll = async () => {
const start = performance.now();
const responses = await Promise.all([
fetch("https://httpbin.org/delay/1"),
fetch("https://httpbin.org/delay/1"),
fetch("https://httpbin.org/delay/1"),
]);
const elapsed = (performance.now() - start) / 1000;
alert(`3件取得完了: ${responses.length}件、経過時間 ${elapsed.toFixed(2)}秒`);
};
button.addEventListener("click", fetchAll);
ブラウザで index.html を再読み込みし、「クリック」ボタンを押すと、以下のようなポップアップが表示されます(経過時間の数値は環境によって変動します)。

「このページの内容」のタイトルの下に「3件取得完了: 3件、経過時間 X.XX秒」の形でポップアップが表示されます。3件のリクエストが並行に開始され、すべての完了をまとめて 1 回の await で待てていることが動作確認のポイントで、Promise.all に渡した配列の要素がそれぞれ並行実行されていることが確認できます。1件ずつ直列に await した場合は3件×1秒+通信オーバーヘッドで少なくとも 3 秒以上かかるところ、並行実行では最も遅い1件分の時間 + オーバーヘッド程度で完了するため、通信時間を大きく短縮できます(ネットワーク状況やサーバ側の応答速度によっては、直列との差が縮まって見える場合もあります)。
コードを解説します。
const start = performance.now();
performance.now() は、ページ読み込みからの経過時間をミリ秒単位で返すブラウザ組み込みの関数です。処理開始時刻を控えるために使っています。
const responses = await Promise.all([
fetch("https://httpbin.org/delay/1"),
fetch("https://httpbin.org/delay/1"),
fetch("https://httpbin.org/delay/1"),
]);
配列に渡した3つの fetch は、Promise.all に渡した時点で並行して開始されます。await は3件すべての解決を待ってから、Response オブジェクトの配列を返します。
const elapsed = (performance.now() - start) / 1000;
alert(`3件取得完了: ${responses.length}件、経過時間 ${elapsed.toFixed(2)}秒`);
処理完了時の時刻から開始時刻を引き、ミリ秒を秒に直して表示しています。toFixed(2) で小数第2位までに整形しています。
| 💡 ポイント |
|---|
Promise.all は1つでも失敗するとすべてが失敗扱いになります。1件の失敗で他の結果まで捨てたくない場合は、Promise.allSettled を使うと成功・失敗を含めた結果配列を得られます。 |
5. まとめ
この章では、JavaScriptの応用構文を学びつつ、実際にDOM操作・API呼び出しを組み合わせて動くWebページを実装する流れを体験しました。
<script>タグにdeferを付けると、HTMLの読み込みが完了してからスクリプトが実行されるため、<body>内の要素を安全に参照できるdocument.querySelector/document.querySelectorAllでCSSセレクタと同じ書き方で単一・複数の要素を取得できるtextContentで要素の文字列を読み書きし、classListやstyleでクラス・インラインスタイルを切り替えられるdocument.createElementとappendChildで新しい要素を作ってDOMツリーに追加し、removeで既存要素を削除できるaddEventListenerでユーザ操作に反応するイベントリスナを登録できるfetchは外部サーバへのHTTPリクエストを送り、レスポンスをPromiseで返すasync / awaitを使うと、非同期処理を同期処理と同じ見た目で書けるtry / catchで非同期処理のエラーを1箇所にまとめて捕捉できるPromise.allで複数の非同期処理を並行に実行し、全体の通信時間を短縮できる
次の章では、ここまで学んだDOM操作・fetch・非同期処理を組み合わせて、入力バリデーションや送信中UIを備えた問い合わせフォームをハンズオン形式で体験します。