XMLHttpRequest は、JavaScriptでHTTPリクエストを行うことができる組み込みのブラウザオブジェクトです。
名前の中に「XML」という単語が含まれていますが、XML形式だけでなく、あらゆるデータを操作できます。ファイルのアップロード/ダウンロード、進捗状況の追跡などが可能です。
現在、XMLHttpRequest をある程度非推奨にする、よりモダンなメソッド fetch があります。
現代のWeb開発では、XMLHttpRequest は次の3つの理由で使用されます。
- 歴史的な理由:
XMLHttpRequestを使用した既存のスクリプトをサポートする必要があります。 - 古いブラウザをサポートする必要があり、ポリフィル (例えば、スクリプトを小さく保つため) は使用したくありません。
fetchではまだできないこと、例えば、アップロードの進捗状況を追跡する必要があります。
それは馴染みがありますか?もしそうなら、それで結構です。XMLHttpRequest を進めてください。そうでなければ、Fetchに進んでください。
基本
XMLHttpRequestには、同期と非同期の2つの動作モードがあります。
ほとんどの場合で使用される非同期を最初に見てみましょう。
リクエストを行うには、3つのステップが必要です。
-
XMLHttpRequestを作成するlet xhr = new XMLHttpRequest();コンストラクタには引数はありません。
-
初期化を行います。通常、
new XMLHttpRequestの直後に行います。xhr.open(method, URL, [async, user, password])このメソッドは、リクエストの主なパラメータを指定します。
method– HTTP メソッド。通常は"GET"または"POST"です。URL– リクエスト先の URL。文字列。 URL オブジェクトを使用できます。async– 明示的にfalseに設定すると、リクエストは同期になります。これについては後で少し説明します。user,password– 基本 HTTP 認証に必要なログインとパスワード (必要な場合)。
openの呼び出しは、その名前とは異なり、接続を開きません。リクエストを設定するだけですが、ネットワークアクティビティはsendの呼び出しでのみ開始されます。 -
送信します。
xhr.send([body])このメソッドは接続を開き、リクエストをサーバーに送信します。オプションの
bodyパラメータにはリクエストボディが含まれます。GETのような一部のリクエストメソッドにはボディがありません。POSTのような一部のメソッドは、サーバーにデータを送信するためにbodyを使用します。これについては後で例を見てみましょう。 -
レスポンスの
xhrイベントをリッスンします。これら3つのイベントが最も広く使用されています。
load– リクエストが完了したとき (HTTPステータスが 400 や 500 の場合でも)、レスポンスが完全にダウンロードされたとき。error– リクエストができなかったとき。例えば、ネットワークがダウンした場合や無効な URL の場合。progress– レスポンスがダウンロードされている間、定期的にトリガーされ、ダウンロードされた量を報告します。
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // only triggers if the request couldn't be made at all alert(`Network Error`); }; xhr.onprogress = function(event) { // triggers periodically // event.loaded - how many bytes downloaded // event.lengthComputable = true if the server sent Content-Length header // event.total - total number of bytes (if lengthComputable) alert(`Received ${event.loaded} of ${event.total}`); };
完全な例を次に示します。以下のコードは、サーバーから /article/xmlhttprequest/example/load のURLをロードし、進捗状況を出力します。
// 1. Create a new XMLHttpRequest object
let xhr = new XMLHttpRequest();
// 2. Configure it: GET-request for the URL /article/.../load
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. Send the request over the network
xhr.send();
// 4. This will be called after the response is received
xhr.onload = function() {
if (xhr.status != 200) { // analyze HTTP status of the response
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result
alert(`Done, got ${xhr.response.length} bytes`); // response is the server response
}
};
xhr.onprogress = function(event) {
if (event.lengthComputable) {
alert(`Received ${event.loaded} of ${event.total} bytes`);
} else {
alert(`Received ${event.loaded} bytes`); // no Content-Length
}
};
xhr.onerror = function() {
alert("Request failed");
};
サーバーが応答したら、次の xhr プロパティで結果を受け取ることができます。
status- HTTPステータスコード (数値):
200,404,403など。HTTP以外のエラーの場合、0になる可能性があります。 statusText- HTTPステータスメッセージ (文字列): 通常、
200の場合はOK、404の場合はNot Found、403の場合はForbiddenなど。 response(古いスクリプトではresponseTextを使用する場合があります)- サーバーのレスポンスボディ。
対応するプロパティを使用してタイムアウトを指定することもできます。
xhr.timeout = 10000; // timeout in ms, 10 seconds
指定された時間内にリクエストが成功しない場合、キャンセルされ、timeout イベントが発生します。
?name=value のように URL にパラメータを追加し、適切なエンコーディングを確保するために、URL オブジェクトを使用できます。
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'test me!');
// the parameter 'q' is encoded
xhr.open('GET', url); // https://google.com/search?q=test+me%21
レスポンスタイプ
xhr.responseType プロパティを使用して、レスポンスの形式を設定できます。
""(デフォルト) – 文字列として取得、"text"– 文字列として取得、"arraybuffer"–ArrayBufferとして取得 (バイナリデータの場合、ArrayBuffer、バイナリ配列の章を参照)、"blob"–Blobとして取得 (バイナリデータの場合、Blobの章を参照)、"document"– XMLドキュメント (XPath やその他のXMLメソッドを使用できます) または HTMLドキュメント (受信データのMIMEタイプに基づく) として取得、"json"– JSON として取得 (自動的に解析されます)。
例えば、レスポンスをJSONとして取得しましょう。
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// the response is {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
古いスクリプトでは、xhr.responseText や xhr.responseXML プロパティも見られるかもしれません。
これらは、文字列またはXMLドキュメントを取得するための歴史的な理由から存在します。現在では、xhr.responseType で形式を設定し、上記のように xhr.response を取得する必要があります。
準備状態
XMLHttpRequest は、処理が進むにつれて状態を変化させます。現在の状態は xhr.readyState としてアクセスできます。
仕様書に記載されているすべての状態
UNSENT = 0; // initial state
OPENED = 1; // open called
HEADERS_RECEIVED = 2; // response headers received
LOADING = 3; // response is loading (a data packet is received)
DONE = 4; // request complete
XMLHttpRequest オブジェクトは、0 → 1 → 2 → 3 → … → 3 → 4 の順に状態を移行します。状態 3 は、ネットワーク経由でデータパケットが受信されるたびに繰り返されます。
readystatechange イベントを使用して追跡できます。
xhr.onreadystatechange = function() {
if (xhr.readyState == 3) {
// loading
}
if (xhr.readyState == 4) {
// request finished
}
};
readystatechange リスナーは非常に古いコードで見つけることができます。これは歴史的な理由によるもので、load やその他のイベントがなかった時代があったからです。現在では、load/error/progress ハンドラがそれを非推奨にしています。
リクエストの中止
リクエストはいつでも終了できます。xhr.abort() の呼び出しがそれを行います。
xhr.abort(); // terminate the request
これにより、abort イベントが発生し、xhr.status が 0 になります。
同期リクエスト
open メソッドで3番目のパラメータ async が false に設定されている場合、リクエストは同期的に行われます。
つまり、JavaScriptの実行は send() で一時停止し、レスポンスを受信したときに再開します。 alert または prompt コマンドのようなものです。
ここに書き直された例を示します。open の3番目のパラメータは false です。
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/hello.txt', false);
try {
xhr.send();
if (xhr.status != 200) {
alert(`Error ${xhr.status}: ${xhr.statusText}`);
} else {
alert(xhr.response);
}
} catch(err) { // instead of onerror
alert("Request failed");
}
良さそうに見えるかもしれませんが、同期呼び出しは、読み込みが完了するまでページ内の JavaScript をブロックするため、めったに使用されません。一部のブラウザではスクロールが不可能になります。同期呼び出しに時間がかかりすぎると、ブラウザは「ハング」しているWebページを閉じるように提案する可能性があります。
別のドメインからのリクエストやタイムアウトの指定など、XMLHttpRequest の多くの高度な機能は、同期リクエストでは利用できません。また、ご覧のとおり、進捗状況の表示はありません。
以上の理由から、同期リクエストは非常に控えめに、ほとんど使用されません。これ以上は説明しません。
HTTPヘッダー
XMLHttpRequest では、カスタムヘッダーを送信することも、レスポンスからヘッダーを読み取ることもできます。
HTTPヘッダーには3つのメソッドがあります。
setRequestHeader(name, value)-
指定された
nameとvalueでリクエストヘッダーを設定します。例えば
xhr.setRequestHeader('Content-Type', 'application/json');ヘッダーの制限RefererやHostなど、いくつかのヘッダーはブラウザによって排他的に管理されています。完全なリストは、仕様書に記載されています。XMLHttpRequestは、ユーザーの安全性とリクエストの正確性のために、それらを変更することは許可されていません。ヘッダーを削除できないXMLHttpRequestのもう1つの特徴は、setRequestHeaderを取り消すことができないことです。ヘッダーが設定されると、設定されたままになります。追加の呼び出しは、ヘッダーに情報を追加しますが、上書きしません。
例えば
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // the header will be: // X-Auth: 123, 456 getResponseHeader(name)-
指定された
nameでレスポンスヘッダーを取得します (Set-CookieおよびSet-Cookie2を除く)。例えば
xhr.getResponseHeader('Content-Type') getAllResponseHeaders()-
Set-CookieおよびSet-Cookie2を除く、すべてのレスポンスヘッダーを返します。ヘッダーは1行で返されます。例えば
Cache-Control: max-age=31536000 Content-Length: 4260 Content-Type: image/png Date: Sat, 08 Sep 2012 16:53:16 GMTヘッダー間の改行は常に
"\r\n"(OSに依存しません) なので、個々のヘッダーに簡単に分割できます。名前と値の間の区切り文字は、常にコロンとそれに続くスペース": "です。これは仕様書で固定されています。したがって、名前と値のペアを持つオブジェクトを取得する場合は、少しJSを投入する必要があります。
このように (2つのヘッダーが同じ名前を持っている場合、後者が前者を上書きすると仮定)
let headers = xhr .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {}); // headers['Content-Type'] = 'image/png'
POST, FormData
POSTリクエストを行うには、組み込みの FormData オブジェクトを使用できます。
構文
let formData = new FormData([form]); // creates an object, optionally fill from <form>
formData.append(name, value); // appends a field
フォームを作成し、オプションでフォームから入力し、必要に応じてさらにフィールドを append して、その後
xhr.open('POST', ...)–POSTメソッドを使用します。xhr.send(formData)でフォームをサーバーに送信します。
例えば
<form name="person">
<input name="name" value="John">
<input name="surname" value="Smith">
</form>
<script>
// pre-fill FormData from the form
let formData = new FormData(document.forms.person);
// add one more field
formData.append("middle", "Lee");
// send it out
let xhr = new XMLHttpRequest();
xhr.open("POST", "/article/xmlhttprequest/post/user");
xhr.send(formData);
xhr.onload = () => alert(xhr.response);
</script>
フォームは multipart/form-data エンコーディングで送信されます。
または、JSONの方が好きなら、JSON.stringify を使用して文字列として送信します。
ヘッダー Content-Type: application/json を設定することを忘れないでください。多くのサーバーサイドフレームワークは、それでJSONを自動的にデコードします。
let xhr = new XMLHttpRequest();
let json = JSON.stringify({
name: "John",
surname: "Smith"
});
xhr.open("POST", '/submit')
xhr.setRequestHeader('Content-type', 'application/json; charset=utf-8');
xhr.send(json);
.send(body) メソッドは非常に汎用性があります。Blob オブジェクトや BufferSource オブジェクトを含め、ほとんどすべての body を送信できます。
アップロードの進捗状況
progress イベントは、ダウンロード段階でのみトリガーされます。
つまり、何かを POST する場合、XMLHttpRequest は最初にデータ(リクエストボディ)をアップロードし、次にレスポンスをダウンロードします。
大きなものをアップロードしている場合、私たちはアップロードの進捗状況を追跡することに、より関心があるはずです。しかし、xhr.onprogress はここでは役に立ちません。
アップロードイベントを追跡するためだけの、メソッドを持たない別のオブジェクト、xhr.upload があります。
これは xhr と同様のイベントを生成しますが、xhr.upload はアップロード時にのみそれらをトリガーします。
loadstart– アップロードが開始されました。progress– アップロード中に定期的にトリガーされます。abort– アップロードが中止されました。error– HTTPエラーではないエラーです。load– アップロードが正常に完了しました。timeout– アップロードがタイムアウトしました(timeoutプロパティが設定されている場合)。loadend– アップロードが成功またはエラーのいずれかで完了しました。
ハンドラの例
xhr.upload.onprogress = function(event) {
alert(`Uploaded ${event.loaded} of ${event.total} bytes`);
};
xhr.upload.onload = function() {
alert(`Upload finished successfully.`);
};
xhr.upload.onerror = function() {
alert(`Error during the upload: ${xhr.status}`);
};
以下は実際の例です。進捗状況を示すファイルアップロードです。
<input type="file" onchange="upload(this.files[0])">
<script>
function upload(file) {
let xhr = new XMLHttpRequest();
// track upload progress
xhr.upload.onprogress = function(event) {
console.log(`Uploaded ${event.loaded} of ${event.total}`);
};
// track completion: both successful or not
xhr.onloadend = function() {
if (xhr.status == 200) {
console.log("success");
} else {
console.log("error " + this.status);
}
};
xhr.open("POST", "/article/xmlhttprequest/post/upload");
xhr.send(file);
}
</script>
クロスオリジンリクエスト
XMLHttpRequest は、fetch と同じ CORS ポリシーを使用して、クロスオリジンリクエストを作成できます。
fetch と同様に、デフォルトではクッキーや HTTP 認証を別のオリジンに送信しません。それらを有効にするには、xhr.withCredentials を true に設定します。
let xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('POST', 'http://anywhere.com/request');
...
クロスオリジンヘッダーの詳細については、Fetch:クロスオリジンリクエスト の章を参照してください。
まとめ
XMLHttpRequest を使用した GET リクエストの典型的なコード
let xhr = new XMLHttpRequest();
xhr.open('GET', '/my/url');
xhr.send();
xhr.onload = function() {
if (xhr.status != 200) { // HTTP error?
// handle error
alert( 'Error: ' + xhr.status);
return;
}
// get the response from xhr.response
};
xhr.onprogress = function(event) {
// report progress
alert(`Loaded ${event.loaded} of ${event.total}`);
};
xhr.onerror = function() {
// handle non-HTTP error (e.g. network down)
};
実際には、さらに多くのイベントがあり、最新の仕様 でそれら(ライフサイクルの順に)リストされています。
loadstart– リクエストが開始されました。progress– レスポンスのデータパケットが到着しました。現時点でのレスポンスボディ全体がresponseにあります。abort– リクエストはxhr.abort()の呼び出しによってキャンセルされました。error– 接続エラーが発生しました。たとえば、間違ったドメイン名などです。404 などの HTTP エラーでは発生しません。load– リクエストが正常に完了しました。timeout– リクエストがタイムアウトのためキャンセルされました(設定されている場合のみ発生)。loadend–load、error、timeoutまたはabortの後にトリガーされます。
error、abort、timeout および load イベントは相互に排他的です。それらのうちの1つだけが発生します。
最もよく使用されるイベントは、ロード完了(load)、ロード失敗(error)です。または、単一の loadend ハンドラを使用して、リクエストオブジェクト xhr のプロパティを調べて何が起こったかを確認できます。
別のイベント:readystatechange はすでに見てきました。歴史的に、これは仕様が決定されるずっと前に登場しました。今日では、それを使用する必要はありません。新しいイベントで置き換えることができますが、古いスクリプトではよく見られます。
特にアップロードを追跡する必要がある場合は、xhr.upload オブジェクトで同じイベントをリッスンする必要があります。
コメント
<code>タグを使用し、複数行の場合は<pre>タグでラップし、10行以上場合はサンドボックス (plnkr, jsbin, codepen…) を使用してください。