XMLHttpRequestとは

XMLHttpRequestはJavaScriptを使ってブラウザとWEBサーバ間でデータの送受信を行う際に利用することができるオブジェクトです。XMLHttpRequestはWEBサーバからすべてのデータを読み込んだ後でもデータの送受信を行うことができるため、ページをリロードすることなくページ内容を書き換えることが可能になります。XMLHttpRequestを使ったデータの送受信は今のWEBサービスにとって不可欠なものです。現在はWEBサーバとのデータの送受信にXMLHttpRequestを直接利用する機会は少なくaxios(XMLHttpRequestを利用)やfetch関数を利用します。しかしそれらの技術を理解する上でもXMLHttpRequestは重要な機能なので理解を深めておきましょう。

XMLHttpRequestの使用方法

サーバからデータ取得

XMLHttpRequestを使ってサーバからデータを取得してみましょう。ローカルサーバ上の/userにアクセスするとユーザ名が戻されるというシンプルな例を使って動作確認を行います。

WEBサーバの設定

/userにアクセスしてデータを取得するためには、HTTPのGETリクエストを受け取りレスポンスを戻すWEBサーバの設定が必要になります。

本文書ではWEBサーバの設定手順については説明を行っていないため、各自で環境の設定を行う必要があります。

もしLaravelを使用していればweb.phpに下記の記述を行います。


Route::get('/user',function(){

    return 'John Doe';
    
});

サーバを準備できない場合はJSONPlaceHolderを利用することができます。JSONPlaceHolderはダミーのJSONデータを戻してくれる無料のサービスです。https://jsonplaceholder.typicode.com/users/1にアクセスするとユーザ情報をオブジェクトが戻されます。

データ取得のJavaScriptのコード

XMLHttpRequestを使ったJavaScriptのコードは下記のようにscriptタグの中に記述しています。


<script>

// (1)XMLHttpRequestオブジェクトを作成
var xmlHttpRequest = new XMLHttpRequest();

// (2)onreadystatechangeイベントで処理の状況変化を監視
xmlHttpRequest.onreadystatechange = function(){
    if(this.readyState == 4 && this.status == 200){
        console.log(this.responseText);
    }
}

// (3)HTTPのGETメソッドとアクセスする場所を指定
xmlHttpRequest.open('GET','/user',true);

// (4)HTTPリクエストを送信
xmlHttpRequest.send();
</script>

上記のコードを記述したhtmlページを開くと即座にXMLHttpRequestを使ってサーバへのアクセスが開始されます。わずか数行のコードでサーバからユーザ名を取得することができます。取得した情報はChromeであればデベロッパーツールのConsoleを利用して確認することができます。

 サーバから情報を取得
サーバから情報を取得

JSONPlaceHolderを利用している場合はopen第2引数に先程記述したURLを設定してください。


xmlHttpRequest.open(
  'GET',
  'https://jsonplaceholder.typicode.com/users/1',
  true
);
ユーザ情報取得
ユーザ情報取得

XMLHttpRequestの処理の流れ

先程のJavaScriptコードの処理内容を理解するために(1)から(4)の処理を詳細に確認していきます。

(1)XMLHttpRequestオブジェクトを作成


// (1)XMLHttpRequestオブジェクトを作成
var xmlHttpRequest = new XMLHttpRequest();

XMLHttpRequestオブジェクトを利用してブラウザとサーバ間で通信を行うためにXMLHttpRequestインスタンスの作成を行なっています。

(2)onreadystatechangeイベントでリクエストの状況を監視


// (2)onreadystatechangeイベントで処理の状況変化を監視
xmlHttpRequest.onreadystatechange = function(){
    if(this.readyState == 4 && this.status == 200){
        console.log(this.responseText);
    }
}

onreadystatechangeイベントを使うとXMLHttpRequestで行われている処理の状況の変化を監視することができます。イベントを使って状況の変化を監視しているため、サーバからのレスポンス(データを受信)が完了したことを検知することができます。さらにレスポンスが持っているHTTPのステータスコードが200であることを確認しています。

readyStateは処理の状況を表す0から4の数字が割り当てられ、4はリクエスト処理の完了を表しています。readyStateが4でステータスコードが200ならconsole.logコマンドを使って受信したデータを出力します。

readyStateの数字については後ほど説明します。

XMLHttpRequestでは非同期でサーバとの通信を行なっているため処理の完了を待つ同期処理とは異なり、いつ処理が完了になるかわかりません。そのため、onreadystatechangeイベントを使って処理の状況を監視する仕組みが必要になります。

(3)HTTPのGETメソッドとアクセスする場所を指定


// (3)HTTPのGETメソッドとアクセスする場所を指定
xmlHttpRequest.open('GET','/user',true);

HTTPのGETメソッドを使ってローカルサーバ上の/userのURLを指定しています。ブラウザからサーバへリクエストを送る際、情報を取得する場合はGETメソッド、情報を送信する場合はPOSTメソッドを利用します。

第三引数のtrueは非同期かどうかの設定です。非同期だとtrue, 同期だとfalseを設定します。

XMLHttpRequestのopenメソッドの書式は下記のとおりです。メソッドとURLの指定は必須ですが、残りはオプション設定です。user, passwordはbasic認証が設定されている場合に使用します。


XMLHttpRequest.open('HTTPメソッド','URL',['非同期設定',user,password])
basic認証はWEBサーバ上でフォルダ毎にユーザIDをパスワードを使って行う簡易的な認証方法です。

(4)HTTPリクエストを送信


// (4)HTTPリクエストを送信
xmlHttpRequest.send();

(3)ではメソッドとURLを指定し、ここで実際にリクエスト送信を行います。(4)でサーバに対して行ったリクエストは、サーバからのレスポンスを待ちます。サーバからのレスポンスは(2)のイベント設定を使うことでレスポンスの完了がわかります。

onreadystatechangeイベントについて

処理状況が変化する度にreadystatechangeイベントが発生することでコード側でその変化に応じた処理を行うことができます。現在の状況は、readyStateを通して判断することができます。readyStateは下記のように0から4の値が割り当てられており、数字によって意味が異なります。

  • readyState=0 : 状況=UNSENT: 初期状態(インスタンス作成)
  • readyState=1 : 状況=OPENED: openメソッド実行
  • readyState=2 : 状況=HEADERS_RECEIVED: レスポンスヘッダー受信
  • readyState=3 : 状況=LOADING: データ受信中
  • readyState=4 : 状況=DONE: リクエスト完了

実際に処理の状態が変化する度にonreadystatechangeイベントが発生するのかコードを使って確認します。onreadystatechange以外のコードについては先程説明を行ったコードと同じです。


xmlHttpRequest.onreadystatechange = function(){   
    if(this.readyState == 0){
        console.log('UNSENT:初期状態')
    }            
    if(this.readyState == 1){
        console.log('OPENED:openメソッド実行')
    }            
    if(this.readyState == 2){
        console.log('HEADERS_RECEIVED:レスポンスヘッダー受信')
    }            
    if(this.readyState == 3){
        console.log('LOADING:データ受信中')
    }
    if(this.readyState == 4){
        console.log('DONE:リクエスト完了')
    }
}

下記のように状況の変化がイベントにより取得することがわかります。しかし、readyStateの0がありません。これはreadyStateは0は初期状態を表しており、0から1に状況が変化をした時にはじめてonreadystatechangeイベントが発生するためです。

onreadystatechangeによる状況変化
onreadystatechangeによる状況変化

JSONデータの受信

先程はJohn Doeという文字列を受け取りましたが、外部との通信を行う際はJSONでデータが送られてくる場合が大半です。サーバから送信するデータをJSONに変更します。

サーバ側の処理は環境により異なりますがここではLaravelを使っているので、配列をreturnすると自動でJSONに変換されます。


Route::get('/user',function(){
    return ['id'=> 1, 'user_name' => 'John Dow'];
});

受け取ったJSONデータを使ってユーザ名をconsole.logに表示するコードを記述します。


xmlHttpRequest.onreadystatechange = function(){

    if(this.readyState == 4 && this.status == 200){
        user = this.responseText;
        console.log(user.user_name);
    }
}

しかし、コンソールには、undefinedと表示されユーザ名は表示されません。this.responseTextにはJSONデータが文字列で入っておりオブジェクトとして扱えるようにするためにはJSON.parseを実行する必要があります。


user = JSON.parse(this.responseText);

JSON.parseを実行するとJohn Doeが表示されます。

Response Typeについて

Response Typeを設定すると設定したフォーマットでresponseデータを取得することができます。そのため、Reponse Typeをjsonに設定するとJSON.parseを実行する必要がなくなります。しかし、値を取得する際はresponseTextではなくresponseで取得することになります。


var xmlHttpRequest = new XMLHttpRequest();

xmlHttpRequest.responseType = 'json';

xmlHttpRequest.onreadystatechange = function(){

    if(this.readyState == 4 && this.status == 200){
        user = this.response;
        console.log(user.user_name);
    }
}

JSON.parseを実行することなく、John Doeが表示されます。

Response Typeはblob, textなどjson以外の値も設定することができます。現在はResponse Typeを取得してデータの取得を行います。

responseのヘッダー情報

サーバからのresponseのヘッダー情報はgetAllResponseHeaders()で確認することができます。


console.log(user.user_name);
console.log(this.getAllResponseHeaders());
getAllResponseHeaders()メソッドの結果
getAllResponseHeaders()メソッドの結果

イベントについて

処理の状況を確認するためにはonreadystatechangeイベントを利用してきましたが、別のイベントを設定することで同じように状況の変化を確認することができます。

onreadystatechangeイベント以外に利用できるイベントには下記のようなものがあります。

  • onloadstart : リクエストが開始した時
  • onprogress : リクエストが完了するまで周期的に実行される。受信したデータを確認することも可能。
  • onabort : abortメソッドによってリクエストが中断された時
  • onerror : リクエストでエラーが発生した時
  • onload : リクエストが完了した時
  • ontimeout : リクエストがタイムアウトした時

onreadystatechangeイベントを実行した時のように下記の6つのイベントを設定し動作するのか確認を行います。


xmlHttpRequest.onloadstart = function(){
    console.log('onloadstartイベント');
};

xmlHttpRequest.onprogress = function(){
    console.log('onprogressイベント');
};

xmlHttpRequest.onload = function(){
    console.log('onloadイベント');
};

xmlHttpRequest.onabort = function(){
    console.log('onabortイベント');
};

xmlHttpRequest.onerror = function(){
    console.log('onerrorイベント');
};

xmlHttpRequest.ontimeout = function(){
    console.log('ontimeoutイベント');
};

正常にGETリクエストが完了した時には、下記の3つのイベントのみ実行されることが確認できます。

イベントの動作確認
イベントの動作確認

実行されなかったイベントの動作も確認していきます。まずonabortイベントを確認します。コードの中でXMLHttpRequestはabortメソッドを持っているので、それを利用します。データを送信した直後にabortメソッドを実行します。


xmlHttpRequest.send();

xmlHttpRequest.abort();

リクエストを開始したonloadstartの後にonabortイベントが発生していることが確認できます。

onabortメソッドを実行した結果
onabortメソッドを実行した結果

つぎにontimeoutイベントを確認します。リクエストのタイムアウトを設定します。ミリセカンドの単位なので、リクエスト後に2000(=2秒)サーバから返答がないとイベントが実行されます。


xmlHttpRequest.timeout = 2000

サーバ側でレスポンスを遅らせる処理を行います。ここでは、PHPのsleep関数を使います。


sleep(3);

onloadstartイベントはすぐに表示されますが、ontimeoutイベントが表示されるまでには設定した2秒待つ必要があります。

ontimeoutイベント
ontimeoutイベント

onerrorイベントはネットワークレベルの障害が発生した場合に実行されます。HTTPのリクエストのエラーでは実行されません。

ここでは存在しないサーバのIPアドレスの設定を行います。


xmlHttpRequest.open('GET','http://127.0.0.2',true);

onloadstrartイベントが表示されてからしばらくするとonerrorイベントが表示されます。

onerrorイベント発生
onerrorイベント発生

ステータスコードを利用したエラー処理

ネットワークレベルのエラーはonerrorイベントで確認できることがわかりました。サーバにはアクセスできるがページが存在しない404エラーが発生した場合はどのように対応するのでしょう。その場合はリエクスとが完了した時に実行されるonloadイベントで確認することができます。

onloadイベントが実行された場合にHTTPのステータスコードを表示させます。


xmlHttpRequest.onload = function(){
    console.log('onloadイベント');
    console.log(this.status)
};

ページが存在しない/user2にアクセスするとNot Found404でステータスコード404が戻ってくることが確認できます。

404 Not Found
404 Not Found

HTTPレベルでエラーが発生した場合は、このステータスコードを利用することできます。正常終了のHTTPコード200の場合以外は別の処理を行うといったことが可能です。


xmlHttpRequest.onload = function(){
    if(this.status != 200){
    console.log(this.status + ':エラーが発生しています。');
    }else{
        console.log(this.status + '正常に動作しました。');
    }
};

ステータスコード404だけではなくInternal Server Eroroの500も処理することができます。

Internal Server Errorが発生した場合
Internal Server Errorが発生した場合

POSTリクエストでデータ送信

サーバへのデータ送信

次は、XMLHttpRequestを使ってサーバにデータを送信してみましょう。サーバにデータを送信するためにはPOSTリクエストを利用します。POSTリクエストを使うためには、openメソッドでpostを指定します。


xmlHttpRequest.open('POST','/api/data',true);

サーバ設定

データを受け取る側のサーバ設定を各環境に応じて行う必要があります。もし、Laravelを使用していればapi.phpに下記の記述を行い、サーバ側で受信したデータを表示させます。


Route::post('/data',function(){

    dd(request()->all());

});

データ送信のJavaScriptのコード

サーバに非同期でデータを送信するためのJavaScriptコードは下記の通りです。


<script>

    // (1)XMLHttpRequestオブジェクトを作成
    var xmlHttpRequest = new XMLHttpRequest();

    // (2)HTTPのPOSTメソッドとアクセスする場所を指定
    xmlHttpRequest.open('POST','/api/data',true);

    // (3)送信する内容の形式を設定
    xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');

    // (4)HTTPリクエストを送信
    xmlHttpRequest.send('id=2&user_name=Mike');

</script>

POSTメソッドでデータ送信した内容をサーバ側で確認することができます。

サーバ側で受け取った内容
サーバ側で受け取った内容

POSTリクエストの処理の流れ

先程のJavaScriptコードの中でどのような処理がおこなわれているか理解するために(1)から(4)の処理の内容を詳細に見ていきます。

(1)XMLHttpRequestオブジェクトを作成


<script>
    // (1)XMLHttpRequestオブジェクトを作成
    var xmlHttpRequest = new XMLHttpRequest();
</script>

XMLHttpRequestオブジェクトを利用してブラウザとサーバ間で通信を行うためにXMLHttpRequestインスタンスの作成を行なっています。

(2)HTTPのPOSTメソッドとアクセスする場所を指定


<script>
    // (2)HTTPのPOSTメソッドとアクセスする場所を指定
    xmlHttpRequest.open('POST','/api/data',true);
</script>

HTTPのPOSTメソッドを使ってローカルサーバ上の/api/dataへのURLを指定しています。

(3)送信する内容の形式を設定


<script>
    // (3)送信する内容の形式を設定
    xmlHttpRequest.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
</script>

送信するデータの形式を指定しています。今回は、key1=value1&key2=value2の形式でデータ送信を行っているので、Content-Typeはapplication/x-www-form-urlencodedを指定しています。

もし指定がない場合は、サーバ側ではデータの受信を行うことができません。

(4)HTTPリクエストを送信


  // (4)HTTPリクエストを送信
    xmlHttpRequest.send('id=2&user_name=Mike');

サーバに送信するデータ内容を設定しています。設定値は、id=2&user_name=Mikeです。

FormDataを利用した送信方法

FormDataオブジェクトを利用してデータの送信を行うことができます。FormDataを使って先程と同様にPOSTでidとuser_nameを送信するコードは下記のようになります。


var xmlHttpRequest = new XMLHttpRequest();

var formData = new FormData();

formData.append("id", 3);
formData.append("user_name","David");

xmlHttpRequest.open('POST','/api/data',true);

xmlHttpRequest.send(formData);

FormDataのappendメソッドを使用することで送信するデータを追加することができます。また、FormDataを使用するとContent-Typeを設定するためのsetRequestHeaderが必要ありません。

入力フォームを利用してPOSTリクエストを送りたい場合は下記のように行うことができます。

html側に入力フォームの作成を行い、送信ボタンを押すとclickイベントによりsendPost関数が実行されます。


<form name="user">
    Id:<input type="text" name="id"><br>
    ユーザ名:<input type="text" name="user_name"><br>
    <input type="submit" onclick="sendPost();return false;">送信</button>
</form>

sendPost関数の中では、入力フォームの内容はdocument.forms.userを使って取得し、FormDataの中に入れます。formDataをsendすることで入力フォームの内容がサーバに送信(POSTリクエスト)されます。


function sendPost(){

    var xmlHttpRequest = new XMLHttpRequest();

    var formData = new FormData(document.forms.user);

    xmlHttpRequest.open('POST','/api/data',true);

    xmlHttpRequest.send(formData);

}