同期と非同期のリクエスト

XMLHttpRequest は同期及び非同期通信の両方に対応しています。しかし、一般的には性能上の理由により、同期リクエストより非同期リクエストを推奨するべきです。

同期リクエストはプログラムの実行をブロックし、画面を「フリーズ」させたりユーザー操作が反応しない状態にしたりすることがあります。

非同期リクエスト

非同期 XMLHttpRequest を使用すると、データが到着したときにコールバックを受け取ります。これにより、リクエストが処理されている間、ブラウザーは通常通りに動作することができます。

例: コンソールログへファイルを送信する

最も簡単な非同期 XMLHttpRequest の使用法を示します。

var xhr = new XMLHttpRequest();
xhr.open("GET", "/bar/foo.txt", true);
xhr.onload = function (e) {
  if (xhr.readyState === 4) {
    if (xhr.status === 200) {
      console.log(xhr.responseText);
    } else {
      console.error(xhr.statusText);
    }
  }
};
xhr.onerror = function (e) {
  console.error(xhr.statusText);
};
xhr.send(null);

2 行目で第 3 引数を true にすることで、リクエストを非同期に処理することを指定しています。

3 行目はイベントハンドラー関数オブジェクトを生成し、リクエストの onload 属性に割り当てています。このハンドラーは、4 行目でリクエストの readyState を見てトランザクションが完了したかどうかを確認し、もしそうであり、かつ HTTP ステータスが 200 であれば、受信した内容をコンソールにダンプします。エラー発生時には、エラーメッセージが表示されます。

15 行目では実際にリクエストを開始します。コールバック処理は状態が変化するたびに呼び出されます。

例: 外部ファイルを読込む標準的な関数を書く

たくさんの外部ファイルを読み込まなければならないことがあります。これは XMLHttpRequest オブジェクトを非同期で使用して、読み込まれたファイルの内容を指定されたリスナに切り替える標準的な関数です。

function xhrSuccess() {
    this.callback.apply(this, this.arguments);
}

function xhrError() {
    console.error(this.statusText);
}

function loadFile(url, callback /*, opt_arg1, opt_arg2, ... */) {
    var xhr = new XMLHttpRequest();
    xhr.callback = callback;
    xhr.arguments = Array.prototype.slice.call(arguments, 2);
    xhr.onload = xhrSuccess;
    xhr.onerror = xhrError;
    xhr.open("GET", url, true);
    xhr.send(null);
}

使用法:

function showMessage(message) {
    console.log(message + this.responseText);
}

loadFile("message.txt", showMessage, "New message!\n\n");

ユーティリティ関数 loadFile の引数は、 (i) (HTTP の GET リクエストを介して) 読み込む対象の URL、 (ii) XHR 操作の正常終了時に実行する関数、 (iii) 任意で XHR オブジェクトから成功時のコールバック関数に (arguments プロパティで) そのまま渡される追加の引数のリストです。

1 行目では、 XHR 操作の正常終了時に呼び出される関数を宣言しています。これは、 loadFile 関数の呼び出しの中で XHR オブジェクトのプロパティに (11 行目で) 割り当てられたコールバック関数 (この場合は showMessage 関数) を呼び出します。 loadFile 関数の呼び出しに提供される追加の引数は (もしあれば)、コールバック関数の実行時に「適用」されます。

5 行目では、 XHR 操作の完了に失敗した時に呼び出される関数を宣言しています。

11 行目では、 XHR オブジェクトの callback プロパティに loadFile の第 2 引数で渡された成功時のコールバック関数を格納します。

12 行目では、 loadFile の呼び出しで与えられた引数の配列を分割します。第 3 引数以降、残りのすべての引数が集められ、変数 xhr の arguments プロパティに割り当てられ、成功時のコールバック関数 xhrSuccess に渡され、最終的には xhrSuccess 関数から呼び出されるコールバック関数 (この場合は showMessage 関数) に渡されます。

15 行目では、リクエストを非同期に処理することを指示するため、第 3 引数に true を指定しています.

16 行目で実際にリクエストを実行しています。

例: タイムアウトの利用

読み込みが行われるのを待つ間、プログラムが永遠に待つことを防ぐためにタイムアウトを利用することができます。これは次のように、 XMLHttpRequest オブジェクト上の timeout プロパティの値を設定することで行うことができます。

function loadFile(url, timeout, callback) {
    var args = Array.prototype.slice.call(arguments, 3);
    var xhr = new XMLHttpRequest();
    xhr.ontimeout = function () {
        console.error("The request for " + url + " timed out.");
    };
    xhr.onload = function() {
        if (xhr.readyState === 4) {
            if (xhr.status === 200) {
                callback.apply(xhr, args);
            } else {
                console.error(xhr.statusText);
            }
        }
    };
    xhr.open("GET", url, true);
    xhr.timeout = timeout;
    xhr.send(null);
}

ontimeout ハンドラーを設定することで、 "timeout" イベントを処理するコードを追加していることに注意してください。

使用法:

function showMessage (message) {
    console.log(message + this.responseText);
}

loadFile("message.txt", 2000, showMessage, "New message!\n");

ここではタイムアウトを 2000 ミリ秒に設定しています.

Note: timeout の対応は Gecko 12.0 で追加されました。

同期リクエスト

Note: Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), Blink 39.0, Edge 13 以降では、メインスレッド上での同期リクエストはユーザーの使い勝手に悪影響を与えるため、非推奨になっています。

同期 XHR リクエストはしばしばウェブ上でハングアップの原因となります。しかし、開発者は通常、ハングアップが発生するのはネットワークの状態が悪かったり、リモートサーバの応答が遅かったりしたときだけなので、この問題に気づくことはありません。 Synchronous XHR は現在非推奨の状態にあります。開発者は同期 API から離れて、代わりに非同期リクエストを使うことをお勧めします。

timeoutabort 等の XHR の新機能は同期 XHR では許可されていません。実行すると InvalidAccessError が発生するでしょう。

例: HTTP 同期リクエスト

この例は単純な同期リクエストの作成方法を示しています。

var request = new XMLHttpRequest();
request.open('GET', '/bar/foo.txt', false);  // `false` で同期リクエストになる
request.send(null);

if (request.status === 200) {
  console.log(request.responseText);
}

3 行目でリクエストを送信しています。引数が null であることは、 GET リクエストに本文が必要ないことを示しています。

5 行目ではトランザクション完了後,ステータスコードをチェックしています。結果のコードが 200 -- HTTP の "OK" の結果 -- ならば、文書のテキストコンテンツがコンソールに出力されます。

例: ワーカーからの同期 HTTP リクエスト

同期リクエストが通常、実行をブロックしない稀な例として、 Worker 内での XMLHttpRequest の利用があります。

example.html (主ページ):

<!doctype html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>MDN Example</title>
<script type="text/javascript">
  var worker = new Worker("myTask.js");
  worker.onmessage = function(event) {
    alert("Worker said: " + event.data);
  };

  worker.postMessage("Hello");
</script>
</head>
<body></body>
</html>

myFile.txt (XMLHttpRequest 同期呼出しの対象):

Hello World!!

myTask.js (Worker):

self.onmessage = function (event) {
  if (event.data === "Hello") {
    var xhr = new XMLHttpRequest();
    xhr.open("GET", "myFile.txt", false);  // 同期リクエスト
    xhr.send(null);
    self.postMessage(xhr.responseText);
  }
};

Note: 但し、 Worker を使っているため結果的に非同期になります。

これは、バックグラウンドでサーバーとやり取りしたり、一部のコンテンツを先読みしたりするために便利です。例と詳細については Web Worker の利用を参照して下さい。

同期 XHR を使用する場面を Beacon API で対応する

XMLHttpRequest の同期的な使用が置き換えられない場面がいくつかあります。例えば unload, beforeunload, pagehide などのイベントの処理中などです。 fetch() API を keepalive フラグ付きで使用することを検討してください。 fetch API を keepalive フラグ付きで使用できないのであれば、ふつうは快適なユーザー操作を提供するために navigator.sendBeacon() API でこれらの場合に対応することができます。

次の例は、 unload ハンドラー内で同期 XMLHttpRequest を使用してサーバーにデータを送信することを試みる、実験的な分析コードを示しています。この結果、ページのアンロードに遅延が発生します。

window.addEventListener('unload', logData, false);

function logData() {
    var client = new XMLHttpRequest();
    client.open("POST", "/log", false); // 第3引数で同期 XHR を指定しています。 :(
    client.setRequestHeader("Content-Type", "text/plain;charset=UTF-8");
    client.send(analyticsData);
}

sendBeacon() メソッドを使用すると、ユーザーエージェントに機会があるとき、アンロードを遅延させたり次の操作の性能に影響を与えたりすることなくデータがウェブサーバーに非同期で送信されます。

次の例は、 sendBeacon() メソッドを使用してサーバーにデータを送信する実験的な分析コードパターンを示しています。

window.addEventListener('unload', logData, false);

function logData() {
    navigator.sendBeacon("/log", analyticsData);
}

関連情報