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…) を使用してください。