2019.01.27

入門書の次にチャレンジ!JavaScript実践クイズ〜GIFガチャ編〜


この記事では、入門書などを読んで JavaScript の文法をひととおり知ったあとに、より実践的に JavaScript の活用法を学べるクイズを紹介します。応用が効くように、実際の開発で「ありがち」な実装パターンを扱っていきたいと思います。

問題

猫GIFガチャを作ろう

画面が小さい場合は右上の「EDIT ON CODEPEN」をクリックすると CodePen のサイトにジャンプして大きな画面で見られます。

See the Pen JavaScript実践クイズ〜GIFガチャ編〜 by Masahiro Harada (@MasahiroHarada) on CodePen.

後述する仕様の通りに動作するように JavaScript を編集しましょう。
穴埋めになっているので /* Insert code here... */ の箇所にコードを足してください。

HTML と CSS は出来ているので変更しません。

今回使用する API(ruddy-mail.glitch.me)は Glitch で作成しました。コードは以下の通りです。
(次回「もっと見る編」で使用する API のコードも含まれています。)

前提条件

  • jQuery などのライブラリは使用せずに実装してください。
  • 対応ブラウザは Google Chrome 最新版とします。
  • 解答例では ES2015 以降の文法も使用します。
  • 非同期通信には Axios というライブラリを用います。
    (CodePen には事前に読み込んであります。)

仕様

  • 「遊ぶ」ボタンをクリックすると、ランダムに選ばれた猫の GIF 画像が表示されます。
  • すでに表示されている GIF 画像は消えてから新しい画像が表示されます。
  • GIF 画像取得 API(/api/gacha)は以下の形式の JSON を返却します。
{
  "url": "画像のURL"
}

Promise

今回はヒントとして Promise オブジェクトについて簡単に説明します。

API との通信における非同期処理には Promise オブジェクトを使用しています。Promise は今や JavaScript において非同期処理を扱う一般的な手法ですので覚えておきましょう。

API との通信には Axios というライブラリを使っています。そして Axios の通信メソッドは Promise オブジェクトを返却します。

const promise = axios.get('/api/abcdefg');

Promise という英単語は「約束」という意味です。約束とは、未来の出来事を今決めることですね。Promise オブジェクトも同様に、未来の処理を定義します。

const promise = axios.get('/api/abcdefg');
// (1) ここでは通信結果は返ってきていない。
promise.then(response => {
  // (2) ここで通信が返ってきている。
});
// (3) ここでも通信結果は返ってきていない。

axios.get() が返却するのは通信結果ではなく、Promise オブジェクトです。Promise オブジェクトの then メソッドに、非同期処理が終わったときに実行させる関数を渡すことで、通信結果を受け取ることができます。HTTP 通信という非同期処理が終わったとき(つまり未来)に処理してほしいことを「約束」させるわけですね。

const promise = axios.get('/api/abcdefg');

promise.then(response => {
  // 通信成功
});

promise.catch(error => {
  // 通信失敗 500など
});

catch メソッドに関数を渡すことで、非同期処理が失敗したときの挙動を約束させることもできます。

上記のコードはメソッドチェーンで以下のように書くこともできます。

axios.get('/api/abcdefg')
  .then(response => {
    // 通信成功
  })
  .catch(error => {
    // 通信失敗 500など
  });

Promise オブジェクトを変数に入れる必要はないので、こちらの書き方の方が一般的です。

Promise は非同期処理を扱うための汎用的な機能ですので、その用途は HTTP 通信に限定されません。ただやはりよく使うのは通信処理でしょう。

さて、Promise については以上です。クイズを解いてみましょう。

🤔

🤔

🤔

回答例

const playButton = document.getElementById('play');
const resultBlock = document.getElementById('result');
const gifImg = document.getElementById('gif');

playButton.addEventListener('click', function() {
  resultBlock.classList.add('hidden');
  axios.get('https://ruddy-mail.glitch.me/api/gacha')
    .then(response => {
      gifImg.src = response.data.url;
      resultBlock.classList.remove('hidden');
    });
});

解説

今回のポイントは Promise による非同期処理でした。

問題コードにもあるように「遊ぶ」ボタンをクリックしたときの動きを実装するわけですが、処理順は以下のようになります。

  1. resultBlock を隠す
  2. API にリクエストを発行する
  3. API からレスポンスが返却されたら、gifImgsrc 属性にレスポンスの url を設定する
  4. resultBlock を表示する

1 と 4 の表示・非表示処理は、このクイズシリーズでは何度か扱っているのでお分かりかもしれませんが、CSS のクラスで制御しています。

.hidden {
  display: none;
}

2 の API にリクエストを発行するコードはすでに問題コードに記述されているため、1 と 4 の処理を足すと以下のコードになるでしょう。

playButton.addEventListener('click', function() {
  resultBlock.classList.add('hidden');
  axios.get('https://ruddy-mail.glitch.me/api/gacha')
    .then(response => {
      resultBlock.classList.remove('hidden');
    });
});

非同期処理(ここでは API との通信)が終わった後の処理は then メソッドの引数の関数内に記述するのがポイントです。

NG例
axios.get('https://ruddy-mail.glitch.me/api/gacha');
// ここに書いてもダメ。
// 非同期処理が終わっているかどうか分からないし、レスポンスを受け取れない。
resultBlock.classList.remove('hidden');

最後に 3 のレスポンスに含まれる GIF 画像の URL を gifImgsrc 属性に設定します。

axios.get('https://ruddy-mail.glitch.me/api/gacha')
  .then(response => {
    //
  });

response のどこに URL が入っているのでしょうか?console.log で確かめてみます。

レスポンス

Axios の README マニュアルを読んでも分かるかもしれませんが、Axios が提供する非同期通信機能におけるレスポンスオブジェクトは上記のようにヘッダーやステータスコードなどのレスポンスデータを持っていて、その中でも data にレスポンスボディが入っています。

ここではさらにその中の url が欲しいわけですから、コードは以下のようになります。

axios.get('https://ruddy-mail.glitch.me/api/gacha')
  .then(response => {
    gifImg.src = response.data.url;
    resultBlock.classList.remove('hidden');
  });

今回のポイントは、Promise を利用した実装パターンでは非同期処理が完了した後の処理は then メソッドの引数に書くという点です。一般的な、手続き的なプログラムの記述とは少し異なりますが、今や Promise は JavaScript の基本ですからこのパターンにも慣れておきましょう。


以上、Promise を利用した非同期処理のクイズでした。