JavaScriptでAPI:外部データを取得してみよう!( API async await)

完成イメージ

郵便番号を入力し、住所検索ボタンをクリックしたら都道府県・市区・町域が自動で取得できるAPIを作成してみよう

APIで犬の画像をランダムに取得し、ボタンをクリックしたら再度、新しい画像を読み込む

作成ファイル・保存場所

js-domフォルダに以下のファイルを作成してください。

ファイル名内容
js-dom08.htmlボタンクリックの動作練習用HTML/js
js-dom__08.htmlステップアップ練習用HTML/js

JavaScriptは <script> タグ内に直接記述してください(内部JS)

今回のテーマ

async / await を使って、APIから外部データを取得し、順番通りに処理・表示する方法を学びます。
複数の非同期処理を整理して書けるようになり、実務でもよく使われるスタイルを体験します。

目的

  • async functionawait の書き方を理解する
  • 複数の非同期処理を「順番に実行する」しくみを体感する
  • try { ... } catch { ... } でエラー処理をまとめて書けるようにする
  • API(2つのエンドポイント)を使ってデータを連携する

チェックポイント

  • await fetch() を使ってAPIからデータを取得できているか?
  • async function の中で処理を順番に書けているか?
  • try / catch を使ってエラーを1箇所で処理できているか?
  • 2つのAPIを組み合わせて表示(例:画像+日本語名)ができているか?

JavaScript基礎トレーニング

1.郵便番号から住所を取得

郵便番号APIを使って、郵便番号から都道府県・市町村区を表示させます。

エンドポイント:https://zipcloud.ibsnet.co.jp/api/search?zipcode=郵便番号

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>郵便番号で住所を取得</title>
    <style>
        input[type=text] {
            width: 400px;
            padding: 10px 5px;
        }

        #search-btn {
            padding: 8.5px 30px;

        }
    </style>
</head>

<body>
    <h1>郵便番号から住所を取得</h1>

    <input type="text" id="zipcode" placeholder="郵便番号を入力(例: 1000001)">
    <button id="search-btn">検索</button>

    <p id="result">住所がここに表示されます</p>

    <script>
        const btn = document.getElementById('search-btn');
        const result = document.getElementById('result');

        btn.addEventListener('click', async () => {
            const zipcode = document.getElementById('zipcode').value;

            try {
                const res = await fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${zipcode}`);
                const data = await res.json();
                console.log(JSON.stringify(data, null, 2));

                if (data.results) {
                    const address = data.results[0];
                    result.textContent = `${address.address1}${address.address2}${address.address3}`;
                } else {
                    result.textContent = '該当する住所が見つかりませんでした。';
                }

            } catch (error) {
                result.textContent = 'エラーが発生しました。';
                console.error('エラー:', error);
            }
        });
    </script>

</body>

</html>
解答例
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>郵便番号で住所を取得</title>
    <style>
        input[type=text] {
            width: 150px;
            padding: 10px 5px;
            margin-bottom: 10px;
        }

        #search-btn {
            padding: 8.5px 30px;
        }

        label {
            display: inline-block;
            width: 80px;
        }
    </style>
</head>

<body>
    <h1>郵便番号から住所を取得</h1>

    <label for="zipcode">郵便番号</label>
    <input type="text" id="zipcode" placeholder="例: 8000000">
    <button id="search-btn">住所検索</button>

    <!-- 住所表示欄(3分割) -->
    <div id="result">
        <p>
            <label for="pref">都道府県</label>
            <input type="text" id="pref">
        </p>
        <p>
            <label for="city">市区</label>
            <input type="text" id="city">
        </p>
        <p>
            <label for="town">町域</label>
            <input type="text" id="town">
        </p>
    </div>

    <script>
        const btn = document.getElementById('search-btn');

        btn.addEventListener('click', async () => {
            const zipcode = document.getElementById('zipcode').value;

            try {
                const res = await fetch(`https://zipcloud.ibsnet.co.jp/api/search?zipcode=${zipcode}`);
                const data = await res.json();
                console.log(JSON.stringify(data, null, 2));

                if (data.results) {
                    const address = data.results[0];
                    document.getElementById('pref').value = address.address1; // 都道府県
                    document.getElementById('city').value = address.address2; // 市区
                    document.getElementById('town').value = address.address3; // 町域
                } else {
                    alert('該当する住所が見つかりませんでした。');
                }
            } catch (error) {
                alert('エラーが発生しました。');
                console.error('エラー:', error);
            }
        });
    </script>

</body>

</html>

2.犬のランダム画像

ページが読み込まれたら、20枚読み込む

エンドポイント:https://dog.ceo/api/breeds/image/random

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>APIで犬画像を表示しよう</title>
    <style>
        #area {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
            gap: 20px;
        }

        .dog-image {
            width: 100%;
            max-width: 100%;
            aspect-ratio: 1 / 1;
            object-fit: cover;
            box-shadow: 0 0 5px rgba(0, 0, 0, 0.5);
        }
    </style>
</head>

<body>
    <h1>Window Load表示 + ボタン再読み込み</h1>
    <button id="btn">もう一度読み込む</button>
    <p id="area">表示するエリア</p>

    <script>
        const btn = document.getElementById('btn');
        const area = document.getElementById('area');

        //非同期で動く関数を作る宣言
        async function showDogs() {
            area.innerHTML = ''; // 表示をクリア

            try {
                for (let i = 0; i < 20; i++) {
                    const res = await fetch('https://dog.ceo/api/breeds/image/random');
                    const data = await res.json();

                    const img = document.createElement('img');
                    img.src = data.message;
                    img.alt = '犬の画像';
                    img.classList.add('dog-image');

                    area.appendChild(img);
                }
            } catch (error) {
                console.error('エラー:', error);
                area.textContent = '画像の取得に失敗しました。';
            }
        }

        // 画面読み込み時に実行
        window.addEventListener('load', showDogs);

        // ボタンクリック時に再読み込み
        btn.addEventListener('click', showDogs);
    </script>
</body>

</html>

async / await / try-catch とは?

JavaScriptでは、APIからデータを取ってくる処理(fetch)は「非同期(ひどうき)」になります。
非同期とは「すぐには結果が返ってこない」ということです。

async / await の役割

async function 関数名() {
  const res = await fetch('URL');
}
  • async function と書くと、関数の中で await が使えるようになります
  • await fetch(...) は「この結果が返ってくるまで待つよ」という意味
  • 順番通りに処理が進むため、コードが読みやすくなります

async とは?

async「この関数は非同期処理を含みますよ」 という宣言です。
これを付けることで、関数の中で await が使えるようになります。

async function fetchData() {
  // ここで await が使えるようになる
}

async を付けると、その関数は 非同期関数になります自動的に Promiseを返す関数として扱われます通常の関数と区別して、「非同期の処理があるよ」と見てわかるメリットもあります

await とは?

await「この処理が終わるまで、次に進まないで待って!」 という命令です。

const res = await fetch('https://api.example.com/data');

awaitPromise(非同期処理の結果)を待つために使いますfetch() のように時間がかかる処理の完了を「待つ」ことができますこれにより、処理の順番が保たれ、コードの見通しが良くなります

try / catch のセットで使うと安心!

async function fetchData() {
  try {
    const res = await fetch('URL');
    const data = await res.json();
    console.log(data);
  } catch (error) {
    console.error('エラー:', error);
  }
}
  • tryの中でエラーが起きたら catch に飛んでくれる
  • ページ全体が止まらない
  • ユーザーに「エラーメッセージ」を出すことができる

JavaScript応用トレーニング

ボタンをクリックしてランダム表示

2種類のAPIを使ってポケモンを表示させよう(ポケモン+日本語化)

<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>APIを使ってみよう!</title>
</head>

<body>
    <h2>ランダムポケモンを表示</h2>
    <button id="btn">ポケモンを表示</button>
    <p id="result">ここに表示されます</p>


    <script>
        const btn = document.getElementById('btn');
        const result = document.getElementById('result');

        btn.addEventListener('click', async () => {
            try {
                const randomId = Math.floor(Math.random() * 898) + 1; // 1〜898番まで対応
                const res = await fetch(`https://pokeapi.co/api/v2/pokemon/${randomId}`);
                const data = await res.json();
                console.log(JSON.stringify(data, null, 2));//data確認

                const image = data.sprites.other['official-artwork'].front_default;
                // "sprites": {
                //     "other": {
                //         "official-artwork": {
                //             "front_default": "https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/other/official-artwork/25.png"
                //         }
                //     }
                // }
                //なぜここだけブラケット記法['official-artwork']
                //ここはハイフンを含むのでドット記法が使えません
                

                // 日本語名の取得(2回目のfetch)
                const speciesRes = await fetch(data.species.url);
                const speciesData = await speciesRes.json();
                const jpName = speciesData.names.find(n => n.language.name === 'ja').name;

                result.innerHTML = `
                    <p><strong>${jpName}</strong>(No.${randomId})</p>
                    <img src="${image}" width="400" alt="${jpName}">
                `;
            } catch (error) {
                result.textContent = 'エラーが発生しました';
                console.error(error);
            }
        });
    </script>
</body>

</html>

ポケモンAPIの画像は「学習や個人利用」以外には使ってはいけません。

ポケモンを名前で検索できるようにしてみよう!
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>ポケモンを検索しよう!</title>
    <style>
        input[type=text] {
            padding: 7px 10px;
            width: 250px;
        }

        button {
            padding: 4px 20px;
        }
    </style>
</head>

<body>
    <h2>ポケモンを名前で検索</h2>
    <input type="text" id="nameInput" placeholder="ポケモンの名前(例:ピカチュウ)">
    <button id="btn">検索</button>
    <p id="result">ここに表示されます</p>

    <script>
        const btn = document.getElementById('btn');
        const result = document.getElementById('result');
        const nameInput = document.getElementById('nameInput');

        btn.addEventListener('click', async () => {
            const keyword = nameInput.value.trim();
            if (!keyword) {
                result.textContent = '名前を入力してください。';
                return;
            }

            try {
                // 全ポケモンの種別一覧から日本語対応名を探す
                const res = await fetch(`https://pokeapi.co/api/v2/pokemon-species/?limit=10000`);
                const data = await res.json();

                // 各種別データをfetchして、該当する日本語名を探す
                let foundUrl = null;
                for (const entry of data.results) {
                    const speciesRes = await fetch(entry.url);
                    const speciesData = await speciesRes.json();

                    const jpNameEntry = speciesData.names.find(n => n.language.name === 'ja');
                    if (jpNameEntry && jpNameEntry.name === keyword) {
                        foundUrl = speciesData.varieties[0].pokemon.url;
                        break;
                    }
                }

                if (!foundUrl) {
                    result.textContent = '該当するポケモンが見つかりませんでした。';
                    return;
                }

                const pokeRes = await fetch(foundUrl);
                const pokeData = await pokeRes.json();
                const image = pokeData.sprites.other['official-artwork'].front_default;

                result.innerHTML = `
                    <p><strong>${keyword}</strong>(No.${pokeData.id})</p>
                    <img src="${image}" width="400" alt="${keyword}">
                `;
            } catch (error) {
                result.textContent = 'エラーが発生しました。';
                console.error(error);
            }
        });
    </script>
</body>

</html>

ポケモン図鑑と無限スクロールのサンプル

ページを読み込んだ時点で、ポケモンを自動的に表示します。最初は20体ずつ2回分(計40体)を一括で読み込み、ある程度スクロールできる状態を作ります。その後は、ユーザーがページの下までスクロールするたびに、次の20体を追加表示していくことで、無限にスクロールが続いていくような体験にします。この仕組みは「無限スクロール(infinite scroll)」と呼ばれ、SNSや商品一覧ページなどでもよく使われます。(こちらは学習用サイトです。

あわせて読みたい
Pokemon図鑑アプリ ポケモンAPIの無限スクロール ※このページはJavaScriptの学習目的で制作された教材サンプルです。ポケモンの画像・名称などは、株式会社ポケモンおよび関連会社に著作権...

課題:天気予報のAPIを利用して作成してみよう!

解答例
<!DOCTYPE html>
<html lang="ja">

<head>
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <title>現在の天気を取得しよう</title>
</head>

<body>
  <h2>現在の天気を取得しよう!(Open-Meteo API)</h2>
  <p><button id="weather-btn">天気を取得</button></p>
  <div id="weather-area">ここに結果が表示されます</div>
  <script>
    // weathercode を日本語の天気に変換
    function translateWeatherCode(code) {
      const map = {
        0: '快晴',
        1: '晴れ',
        2: '一部曇り',
        3: '曇り',
        45: '霧',
        48: '霧(霧氷)',
        51: '弱い霧雨',
        53: '中程度の霧雨',
        55: '強い霧雨',
        61: '弱い雨',
        63: '中程度の雨',
        65: '強い雨',
        71: '弱い雪',
        73: '中程度の雪',
        75: '強い雪',
        80: '弱いにわか雨',
        81: 'にわか雨',
        82: '強いにわか雨',
        95: '雷雨',
        96: '雷雨(弱い雹)',
        99: '雷雨(強い雹)'
      };
      return map[code] || '不明';
    }

    // 天気取得ボタンの処理
    document.getElementById('weather-btn').addEventListener('click', async () => {
      const area = document.getElementById('weather-area');
      area.textContent = 'データ取得中...';
      try {
        const lat = 33.59;
        const lon = 130.40;
        const res = await fetch(`https://api.open-meteo.com/v1/forecast?latitude=${lat}&longitude=${lon}&current_weather=true`);
        const data = await res.json();
        console.log(JSON.stringify(data, null, 2)); // デバッグ用

        const temp = data.current_weather.temperature;
        const wind = data.current_weather.windspeed;
        const code = data.current_weather.weathercode;
        const weather = translateWeatherCode(code);

        area.innerHTML = `
        <p><strong>現在の気温:</strong>${temp}℃</p>
        <p><strong>風速:</strong>${wind} m/s</p>
        <p><strong>天気:</strong>${weather}</p>
      `;
      } catch (error) {
        area.textContent = '天気情報の取得に失敗しました。';
        console.error('エラー:', error);
      }
    });
  </script>
</body>

</html>