HTTP通信

ローカルサーバーでアプリケーションが実行できるようになったので、次はGitHubのAPIを呼び出す処理を実装していきます。 GitHubのAPIを呼び出すためにはHTTP通信をする必要があります。 ウェブブラウザ上でJavaScriptからHTTP通信するために、Fetch APIという機能を使います。

Fetch API

Fetch APIはHTTP通信を行ってリソースを取得するためのAPIです。 Fetch APIを使うことで、ページ全体を再読み込みすることなく指定したURLからデータを取得できます。 Fetch APIは同じくHTTP通信を扱うXMLHttpRequestと似たAPIですが、より強力で柔軟な操作が可能です。

リクエストを送信するためには、fetchメソッドを利用します。 fetchメソッドは引数で指定したURLに対して、HTTPリクエストを行えます。

GitHubにはユーザー情報を取得するAPIとして、https://api.github.com/users/GitHubユーザーIDというURLが用意されています。 GitHubのユーザーIDには、英数字と-(ハイフン)以外は利用できないため、ユーザーIDはencodeURIComponent関数を使ってエスケープしたものを結合します。encodeURIComponent/%などURLとして特殊な意味を持つ文字列をただの文字列として扱えるようにエスケープする関数です。

次のコードでは、指定したGitHubユーザーIDの情報を取得するURLに対してfetchメソッドで、GETのHTTPリクエストを行っています。

const userId = "任意のGitHubユーザーID";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`);

レスポンスの受け取り

GitHubのAPIに対してHTTPリクエストを送信しましたが、まだレスポンスを受け取る処理を書いていません。 次はサーバーから返却されたレスポンスのログをコンソールに出力する処理を実装します。

fetchメソッドはPromiseを返します。このPromiseインスタンスはリクエストのレスポンスを表すResponseオブジェクトでresolveされます。 送信したリクエストにレスポンスが返却されると、thenコールバックが呼び出されます。

次のように、Responseオブジェクトのstatusプロパティからは、HTTPレスポンスのステータスコードが取得できます。 また、ResponseオブジェクトのjsonメソッドもPromiseを返します。これは、HTTPレスポンスボディをJSONとしてパースしたオブジェクトでresolveされます。 ここでは、書籍用に用意したjs-primer-exampleというGitHubアカウントのユーザー情報を取得しています。

const userId = "js-primer-example";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
    .then(response => {
        console.log(response.status); // => 200
        return response.json().then(userInfo => {
            // JSONパースされたオブジェクトが渡される
            console.log(userInfo); // => {...}
        });
    });

エラーハンドリング

HTTP通信にはエラーがつきものです。 そのためFetch APIを使った通信においても、エラーをハンドリングする必要があります。 たとえば、サーバーとの通信に際してネットワークエラーが発生した場合は、ネットワークエラーを表すNetworkErrorオブジェクトでrejectされたPromiseが返されます。 すなわち、thenメソッドの第二引数かcatchメソッドのコールバック関数が呼び出されます。

const userId = "js-primer-example";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
    .then(response => {
        console.log(response.status);
        return response.json().then(userInfo => {
            console.log(userInfo);
        });
    }).catch(error => {
        console.error(error);
    });

一方で、リクエストが成功したかどうかはResponseオブジェクトのokプロパティで認識できます。 okプロパティは、HTTPステータスコードが200番台であればtrueを返し、それ以外の400や500番台などならfalseを返します。 次のように、okプロパティがfalseとなるエラーレスポンスをハンドリングできます。

const userId = "js-primer-example";
fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
    .then(response => {
        console.log(response.status); 
        // エラーレスポンスが返されたことを検知する
        if (!response.ok) {
            console.error("エラーレスポンス", response);
        } else {
            return response.json().then(userInfo => {
                console.log(userInfo);
            });
        }
    }).catch(error => {
        console.error(error);
    });

ここまでの内容をまとめ、GitHubからユーザー情報を取得する関数をfetchUserInfoという名前で定義します。

function fetchUserInfo(userId) {
    fetch(`https://api.github.com/users/${encodeURIComponent(userId)}`)
        .then(response => {
            console.log(response.status);
            // エラーレスポンスが返されたことを検知する
            if (!response.ok) {
                console.error("エラーレスポンス", response);
            } else {
                return response.json().then(userInfo => {
                    console.log(userInfo);
                });
            }
        }).catch(error => {
            console.error(error);
        });
}

index.jsでは関数を定義しているだけで、呼び出しは行っていません。

ページを読み込むたびにGitHubのAPIを呼び出すと、呼び出し回数の制限を超えるおそれがあります。 呼び出し回数の制限を超えると、APIからのレスポンスがステータスコード403のエラーになってしまいます。

そのため、HTMLドキュメント側に手動でfetchUserInfo関数を呼び出すためのボタンを追加します。 ボタンのclickイベントでfetchUserInfo関数を呼び出し、取得したいユーザーIDを引数として与えています。 例としてjs-primer-exampleという書籍用に用意したGitHubアカウントを指定しています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="utf-8" />
    <title>Ajax Example</title>
  </head>
  <body>
    <h2>GitHub User Info</h2>

    <button onclick="fetchUserInfo('js-primer-example');">Get user info</button>
    <script src="index.js"></script>
  </body>
</html>

準備ができたら、ローカルサーバーを立ち上げてindex.htmlにアクセスしましょう。 ボタンを押すとHTTP通信が行われ、コンソールにステータスコードとレスポンスのログが出力されます。

Fetchで取得したデータの表示

また、開発者ツールのネットワークパネルを開くと、GitHubのサーバーに対してHTTP通信が行われていることを確認できます。

開発者ツールでHTTP通信の記録を確認する

[コラム] XMLHttpRequest

XMLHttpRequestXHR)はFetch APIと同じくHTTP通信を行うためのAPIです。 Fetch APIが標準化される以前は、ブラウザとサーバーの間で通信するにはXHRを使うのが一般的でした。 このセクションで扱ったFetch APIによるfetchUserInfo関数は、XHRを使うと次のように書けます。

function fetchUserInfo(userId) {
    // リクエストを作成する
    const request = new XMLHttpRequest();
    request.open("GET", `https://api.github.com/users/${encodeURIComponent(userId)}`);
    request.addEventListener("load", () => {
        // リクエストが成功したかを判定する
        // Fetch APIのresponse.okと同等の意味
        if (request.status >= 200 && request.status < 300) {
            // レスポンス文字列をJSONオブジェクトにパースする
            const userInfo = JSON.parse(request.responseText);
            console.log(userInfo);
        } else {
            console.error("エラーレスポンス", request.statusText);
        }
    });
    request.addEventListener("error", () => {
        console.error("ネットワークエラー");
    });
    // リクエストを送信する
    request.send();
}

Fetch APIはXHRを置き換えるために作られたもので、ほとんどのユースケースではXHRを使う必要はなくなりました。 また、古いブラウザではFetch APIが実装されていなかったため、ブラウザの互換性を保つためにXHRが使われている場面もありました。 XHRの詳しい使い方については、XHRの利用についてのドキュメントを参照してください。

このセクションのチェックリスト

  • Fetch APIを使ってHTTPリクエストを送った
  • GitHubのAPIから取得したユーザー情報のJSONオブジェクトをコンソールに出力した
  • Fetch APIの呼び出しに対するエラーハンドリングを行った
  • fetchUserInfo関数を宣言し、ボタンのクリックイベントで呼び出した

ここまでのアプリは次のURLで確認できます。