同期と非同期のリクエスト
XMLHttpRequest
は同期と非同期通信の両方に対応しています。しかし、一般的には性能上の理由により、同期リクエストより非同期リクエストを選択すべきです。
同期リクエストはプログラムの実行をブロックし、画面を「フリーズ」させたりユーザー操作が反応しない状態にしたりすることがあります。
非同期リクエスト
非同期 XMLHttpRequest
を使用すると、データが到着したときにコールバックを受け取ります。これにより、リクエストが処理されている間、ブラウザーは通常通りに動作することができます。
例: コンソールログへファイルを送信する
最も簡単な非同期 XMLHttpRequest
の使用法を示します。
const xhr = new XMLHttpRequest();
xhr.open("GET", "/bar/foo.txt", true);
xhr.onload = (e) => {
if (xhr.readyState === 4) {
if (xhr.status === 200) {
console.log(xhr.responseText);
} else {
console.error(xhr.statusText);
}
}
};
xhr.onerror = (e) => {
console.error(xhr.statusText);
};
xhr.send(null);
xhr.open
の行で第 3 引数を true
にすることで、リクエストを非同期に処理することを指定しています。
それからイベントハンドラー関数オブジェクトを生成し、リクエストの onload
属性に割り当てています。このハンドラーは、リクエストの readyState
を見てトランザクションが完了したかどうかを確認し、もしそうであり、かつ HTTP ステータスが 200 であれば、受信した内容をコンソールにダンプします。エラー発生時には、エラーメッセージが表示されます。
xhr.send
の呼び出しでは実際にリクエストを開始します。コールバック処理は状態が変化するたびに呼び出されます。
例: 外部ファイルを読込む標準的な関数を書く
たくさんの外部ファイルを読み込まなければならないことがあります。これは XMLHttpRequest
オブジェクトを非同期で使用して、読み込まれたファイルの内容を指定されたリスナーに切り替える標準的な関数です。
function xhrSuccess() {
this.callback(...this.arguments);
}
function xhrError() {
console.error(this.statusText);
}
function loadFile(url, callback, ...args) {
const xhr = new XMLHttpRequest();
xhr.callback = callback;
xhr.arguments = args;
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
プロパティで) そのまま渡される追加の引数のリストです。
まず、関数 xhrSuccess
を宣言し、 XHR 処理が正常に完成させた際に呼び出されるようにします。この関数は、loadFile
関数の呼び出し時に指定されたコールバック関数(この場合は関数 showMessage
)を呼び出します。このコールバック関数は、 XHR オブジェクトのプロパティに割り当てられています。関数 loadFile
の呼び出し時に追加で渡された引数(存在する場合)は、コールバック関数の実行時に「適用」されます。 XHR 処理が正常に完成されなかった場合、 xhrError
関数が呼び出されます。
loadFile
の第 2引数として指定された成功コールバックは、 XHR オブジェクトの callback
プロパティに格納されます。 3 つ目以降の引数、 loadFile
の残りの引数はすべて(残余引数構文を使用して)収集され、変数 xhr
の arguments
プロパティに割り当てられ、成功コールバック関数 xhrSuccess
に渡され、最終的に xhrSuccess
関数から呼び出されるコールバック関数(この場合は showMessage
)に渡されます。
xhr.open
の呼び出しでは、 3 つ目(引数)に true
を指定することで、リクエストを非同期で処理することを示します。
最後に、 xhr.send
が実際にリクエストを開始します。
例: タイムアウトの利用
読み込みが行われるのを待つ間、プログラムが永遠に待つことを防ぐためにタイムアウトを利用することができます。これは次のように、 XMLHttpRequest
オブジェクト上の timeout
プロパティの値を設定することで行うことができます。
function loadFile(url, timeout, callback, ...args) {
const xhr = new XMLHttpRequest();
xhr.ontimeout = () => {
console.error(`The request for ${url} timed out.`);
};
xhr.onload = () => {
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 ミリ秒に設定しています.
同期リクエスト
警告: 同期 XHR リクエストは、特にネットワークの状態が悪かったり、リモートサーバーの応答が遅かったりしたときに、しばしばウェブ上でハングアップの原因となります。同期 XHR は非推奨であり、使用しないようにして、代わりに非同期リクエストを使用してください。
timeout
や abort
等の XHR の新機能は同期 XHR では許可されていません。実行すると InvalidAccessError
が発生するでしょう。
例: HTTP 同期リクエスト
この例は単純な同期リクエストの作成方法を示しています。
const request = new XMLHttpRequest();
request.open("GET", "/bar/foo.txt", false); // `false` で同期リクエストになる
request.send(null);
if (request.status === 200) {
console.log(request.responseText);
}
request.send
の呼び出しで、リクエストを送信しています。引数が null
であることは、 GET
リクエストに本文が必要ないことを示しています。
if
文ではトランザクション完了後,ステータスコードをチェックしています。結果のコードが 200 — HTTP の "OK" の結果 — ならば、文書のテキストコンテンツがコンソールに出力されます。
例: ワーカーからの同期 HTTP リクエスト
同期リクエストが通常、実行をブロックしない稀な例として、 Worker
内での XMLHttpRequest
の利用があります。
example.js
(メインページで呼び出すスクリプト):
const worker = new Worker("myTask.js");
worker.onmessage = (event) => {
console.log(`Worker said: ${event.data}`);
};
worker.postMessage("Hello");
myFile.txt
(XMLHttpRequest
同期呼出しの対象):
Hello World!!
myTask.js
(Worker
):
self.onmessage = (event) => {
if (event.data === "Hello") {
const xhr = new XMLHttpRequest();
xhr.open("GET", "myFile.txt", false); // 同期リクエスト
xhr.send(null);
self.postMessage(xhr.responseText);
}
};
メモ:
Worker
を使っているため、結果的には非同期になります。
これは、バックグラウンドでサーバーとやり取りしたり、一部のコンテンツを先読みしたりするために便利です。例と詳細については ウェブワーカーの利用を参照して下さい。
同期 XHR を使用する場面をビーコン API で対応する
XMLHttpRequest
の同期的な使用が置き換えられない場面がいくつかあります。例えば unload
, beforeunload
, pagehide
などのイベントの処理中などです。 fetch()
API を keepalive
フラグ付きで使用することを検討してください。 fetch
API を keepalive
フラグ付きで使用できないのであれば、ふつうは快適なユーザー操作を提供するために navigator.sendBeacon()
API でこれらの場合に対応することができます。
次の例は、 unload ハンドラー内で同期 XMLHttpRequest
を使用してサーバーにデータを送信することを試みる、実験的な分析コードを示しています。この結果、ページのアンロードに遅延が発生します。
window.addEventListener("unload", logData, false);
function logData() {
const 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);
}