派遣で働くエンジニアのスキルアップを応援するサイト

PRODUCED BY RECRUIT

JavaScriptを学ぶ上でつまずきやすいポイントを理解するための連載【第5回】Ajaxの利点や書き方

前回前々回と2回にわたり、非同期処理について学びました。実際に非同期処理を駆使するのは、今回の主題であるAjax(エージャックス)との関連であることがほとんどです。3つのポイントと具体的な処理を通して「非同期処理」と「Ajax」の理解を深めましょう。

今回の学び

  • Ajaxの利点
  • Ajaxの実装:GET通信とPOST通信
  • 複数の通信を制御する方法
【筆者】佐藤正志さん
契約社員、客員研究員、フリーランスなどさまざまな立場で開発現場を経験。フロントエンド、バックエンド開発、チームリードやOJTの育成担当まで幅広くこなしてきた。現在プログラミングトレーニング事業を行うサークルアラウンド株式会社を立ち上げ、経営や現場開発を行いながらOJT経験をもとに後進の育成に励んでいる。ノウハウのコンテンツ化にも取り組んでおり、著書に『ステップアップJavaScript』(翔泳社)がある。

*記事内に使用しているサンプルコードは、以下からも確認することができます。
https://github.com/CircleAround/engineer_style_js/tree/main/05

Ajaxを知るポイント1:利点を知る

AjaxはブラウザのJavaScriptから利用できる通信用のAPIを利用して、画面の更新や操作をスムーズに体感させる技術です。まず通常の画面更新を用いた方法との差異を理解しましょう。

■通常の画面更新

上図はAjaxを使用せずにページの更新を行う場合の例です。皆さんがブラウザを利用する際、多くはこの方法で画面遷移をしています。

■Ajaxの画面更新

上図はAjaxの例です。 先の例との大きな違いは、JavaScriptにより「任意のタイミングで通信できる」点です。

  • 要素のクリック(onClickイベント)
  • 画面のスクロール(onScrollイベント)
  • マウスの移動(onMouseMoveイベント) など

JavaScriptのイベントを使ってさまざまな操作をリクエストのトリガーに指定することができます。通信を終えたあと、JavaScriptでDOM操作を行うことで、レスポンスとして返ってきたデータを使用してページのUIを更新することができます。

利点については、以下のような点を抑えると良いでしょう。

  • ボタンやリンク以外のブラウザの操作全般をリクエストのトリガーにできるので、操作性の良いUIにできる
  • 画面全体を更新せず、データのみ受信することから通信量が減り、快適になる
  • 通信している最中でも、画面の他の箇所を操作でき、待ち時間が減る

Ajaxを知るポイント2:Ajaxの基本的な書き方を知る

■GET通信

実際に簡単に動くものを書いてみます。GET通信で情報取得と表示を行ってみましょう。

index.html
・・・
    <button onclick="retriveUsers()">更新</button>
    <ul id="users"></ul>
・・・

HTMLでは更新ボタンを押すとretriveUsers関数を呼び出します。ボタンを押す度に通信して、最新のユーザー情報がulの中に埋め込まれるようにしていきます。

続いてJavaScriptです(エラー処理は省略しています)。

const retriveUsers = async () => {
  const users = await fetchUsers(); // *1
  updateUsers(users); // *2
};

retriveUsers関数は、*1のfetchUsersでユーザーの情報を取得したあと、*2のupdateUsersで画面の更新をします。

const fetchUsers = async () => {
  const res = await fetch("/users.json");
  return await res.json();
};

fetchUsersはasync/awaitを使った非同期処理のコードです。このコードの内容は前回扱いました。

結果、/users.jsonで返されるJSONをObjectに変換したデータが返ります。以下のような、Objectの配列が取得できる想定です。

[{"id":1,"name":"織田信長","prefecture_id":3},{"id":2,"name":"豊臣秀吉","prefecture_id":2},{"id":3,"name":"徳川家康","prefecture_id":1}]

const updateUsers = (users) => {
  const usersList = users.map((user) => `<li>${user.name}</li>`).join("");
  const usersElm = document.getElementById("users");
  usersElm.innerHTML = usersList;
};

updateUsers関数はusers変数の中に入っているユーザーの名前をul#usersに書き込みます。

更新ボタンを押すと通信が行われて、以下のようにユーザー名のリストが表示されます。

このときのポイントは、以下です。

  • ブラウザのURLは全く変わらない(画面全体を取り直さない)
  • 画面全体更新ではなく、ul#users の部分だけが更新される

ブラウザの開発者ツールのNetwork機能を使って通信状況を確認してみましょう。更新ボタンを押す操作を繰り返すと、画面遷移せずに何度もusers.jsonを取得していることが確認できます。

■POST通信

GET通信の次は、POST通信も行ってみましょう。

<button onclick="addUser({ name: 'テスト' })">ユーザー追加</button>

HTMLに新たなボタンを追加しました。クリックするとaddUser{ name: 'テスト' }の引数で呼び出します。

JavaScriptで用意するaddUserは以下のようにしました。

const addUser = async (user) => {
  await fetch("/users", {
    method: "post",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify(user),
  });
};

fetchの第二引数は以下のようなオプションです。POSTの場合にはこれらの内容を設定することが多いでしょう。

「ユーザー追加」を押したあと「更新」を押すと、テストユーザーが追加されていることが確認できるはずです。

Ajaxを知るポイント3:よくある複数の通信の制御方法を知る

アプリケーションの初期化のようなタイミングでは、複数の通信をして準備することが多いです。この動きを模擬してみましょう。

今回は以下のような情報を取得します。

/prefectures.jsonはUserオブジェクトのprefecture_idの数値に対応している都道府県の一覧です。

[{"id":1,"name":"東京都"},{"id":2,"name":"大阪府"},{"id":3,"name":"愛知県"}]

以下のような2つの関数がある前提で進めましょう。関数の動作はそれほど難しくないので 細かい解説は割愛します。

// /prefecture.json の内容をオブジェクトの配列として取得する
const fetchPrefectures = async () => {
  const res = await fetch("/prefectures.json");
  return await res.json();
};

// users と prefectures の情報でliタグのリストを作成して ul#usersを更新する
const updateUserWithDetails = (users, prefectures) => {
  const usersList = users
    .map((user) => {
      // prefecturesから、user.prefecture_idとidが一致するものを選ぶ
      const prefecture = prefectures.find(
        (prefecture) => prefecture.id === user.prefecture_id
      );
      return `<li>${user.name}: ${prefecture?.name || "不明"}</li>`;
    })
    .join("");
  const usersElm = document.getElementById("users");
  usersElm.innerHTML = usersList;
};

あまり考えずに書き進めると、このあと以下のように書いていく場合が多いのではないでしょうか。

const retriveUserWithDetailsSequential = async () => {
  const users = await fetchUsers()
  const prefectures = await fetchPrefectures();
  updateUserWithDetails(users, prefectures);
};

このときの動作は、以下のように順番にリクエストを行っていきます。

ただ、この取り方は無駄が多くなります。/users.json/prefectures.jsonは同時に取ってきても問題ないデータのため、一緒に取ることができると初期化が速くなります。

これらを行うのに 前回ご紹介したPromise.all関数を利用します(「非同期処理の理解が深まるとAjaxの処理を効率良く効果的に書くことができる」と伝わると嬉しいです)。

const retriveUserWithDetails = async () => {
  const promises = [fetchUsers(), fetchPrefectures()];
  const [users, prefectures] = await Promise.all(promises);
  updateUserWithDetails(users, prefectures);
};

それぞれの通信を行うPromiseの配列をpromisesとして作成し、これをPromise.allの引数として渡します。結果は入れた配列のそれぞれのPromiseの結果です。

これで、/users.json/prefectures.jsonにほぼ同時に通信を開始して、両方のレスポンスが返ってきてから画面表示を行えます。通信に時間がかかる場合には特に嬉しいでしょう。

まとめ

  • AjaxはブラウザのJavaScriptが持っている通信機能を使った快適な画面更新の技術です
  • 通信はFetchAPIのfetch関数で行うと、Promiseやasync/awaitベースで行えます
  • 複数同時に通信したい場合にはPromise.allが活用できます

現在では、画面の更新をReact/Vue.jsなどで行うことも多いですが「JavaScriptの通信機能を使って、画面の快適な操作を行う」概念は変わりません。このような外部ライブラリの利用で画面の更新のコストが下がったため、頻繁に利用されるようにもなりました。

▼これまでのJavaScriptを学ぶ上でつまずきやすいポイントを理解するための連載
JS特有の「this」の使い方6つをおさえよう
2つの利用シーンから理解する「クロージャ」活用法
実行処理の図を見て「非同期処理」について知ろう
Promiseを理解しasync/await で読みやすい「非同期処理」を書く