XMLHttpRequest は JavaScript で HTTP リクエストを行うための組み込みのブラウザオブジェクトです。
名前に “XML” という用語を含んでいますが、XML 形式だけでなくあらゆるデータ扱うことができます。ファイルをアップロード/ダウンロードしたり、進捗の追跡など様々なことができます。
現在は XMLHttpRequest を若干非推奨とする、よりモダンなメソッド fetch があります。
モダンweb開発では、XMLHttpRequest は次の3つの理由で使われることがあります。:
- 歴史的な理由:
XMLHttpRequestをもつ既存のスクリプトをサポートする必要がある場合 - 古いブラウザをサポートする必要があるが、 polyfill は使いたくない(e.g. スクリプトのサイズを小さくしたい)場合
fetchがまだできないことをしたい場合. e.g アップロードの進捗を追跡するなど
このような要件を聞いたことがありますか?もしそうなら XMLHttpRequest に進んでください。そうでなければ、Fetch に進むのがよいでしょう。
基本
XMLHttpRequest には2つの操作モードがあります: 同期と非同期です。
先に、ほとんどのケースで使われる非同期を見ていきましょう。
リクエストをするためには、次の3ステップが必要です:
-
XMLHttpRequestを作成します:let xhr = new XMLHttpRequest(); // コンストラクタは引数なし -
初期化をします:
xhr.open(method, URL, [async, user, password])このメソッドは通常
new XMLHttpRequestのすぐ後で呼ばれ、リクエストのメインのパラメータを指定します。:method– HTTPメソッド. たいてい"GET"か"POST"です.URL– リクエストURL。文字列で、URL オブジェクトもOKです。async– 明示的にfalseが指定されている場合、リクエストは同期になります。これについては後ほど説明します。user,password– ベーシック HTTP 認証のユーザとパスワードです(必要に応じて).
open呼び出しに注意してください。その名前とは対照的に、接続をオープンするわけではありません。リクエストを設定するだけで、ネットワーク処理はsend呼び出しでのみ始まります。 -
それを送ります
xhr.send([body])このメソッドは接続をオープンし、リクエストをサーバに送信します。オプションの
bodyパラメータにはリクエストボディが含まれます。GETのようないくつかのリクエストメソッドは body を持ちません。またPOSTなどはデータをサーバに送信するのにbodyを使います。後ほど例を見ていきます。 -
応答に対するイベントをリッスンします
これら3つがもっとも広く使われています:
load– 結果が準備できたとき。404 のような HTTP エラーを含みます。error– リクエストが送信できなかったとき e.g. ネットワークダウン or URL不正progress– ダウンロード中に定期的にトリガーされ、ダウンロードされた量が確認できます。
xhr.onload = function() { alert(`Loaded: ${xhr.status} ${xhr.response}`); }; xhr.onerror = function() { // リクエストがまったく送信できなかったときにだけトリガーされます。 alert(`Network Error`); }; xhr.onprogress = function(event) { // 定期的にトリガーされます // event.loaded - ダウンロードされたバイト // event.lengthComputable = サーバが Content-Length ヘッダを送信した場合は true // event.total - トータルのバイト数(lengthComputable が true の場合) alert(`Received ${event.loaded} of ${event.total}`); };
これは完全な例です。下のコードはサーバから /article/xmlhttprequest/example/load のURLをロードし、進行状況を表示します。:
// 1. new XMLHttpRequest オブジェクトを作成
let xhr = new XMLHttpRequest();
// 2. 設定: URL /article/.../load に対する GET-リクエスト
xhr.open('GET', '/article/xmlhttprequest/example/load');
// 3. ネットワーク経由でリクエスト送信
xhr.send();
// 4. レスポンスを受け取った後に呼び出されます
xhr.onload = function() {
if (xhr.status != 200) { // レスポンスの HTTP ステータスを解析
alert(`Error ${xhr.status}: ${xhr.statusText}`); // e.g. 404: Not Found
} else { // show the result
alert(`Done, got ${xhr.response.length} bytes`); // responseText is the server
}
};
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");
};
サーバーが応答すると、リクエストオブジェクトの次のプロパティで結果を受け取ることができます。:
status- HTTPステータスコード(数値):
200,404,403など。HTTP 以外の失敗の場合は0になります。
statusText
:HTTPステータスメッセージ(文字列): 通常, 200 の場合は OK、404 の場合は Not Fount、403 の場合は Forbidden など。
response(古いスクリプトはresponseTextを使用する場合があります)
:サーバーのレスポンス。
対応するプロパティを使用してタイムアウトを指定することもできます。:
xhr.timeout = 10000; // ms でのタイムアウト, これは 10 秒
リクエストが指定時間内で成功しない場合はキャンセルされ、timeout イベントが発生します。
?name=value のような URL パラメータを渡しつつ、適切なエンコーディングを保証するには、URL オブジェクトが使えます。:
let url = new URL('https://google.com/search');
url.searchParams.set('q', 'test me!');
// パラメータ `q` はエンコードされます
xhr.open('GET', url); // https://google.com/search?q=test+me%21
レスポンスタイプ
レスポンスの形式を設定するには xhr.responseType を使います。:
""(デフォルト) – 文字列として取得,"text"– 文字列として取得,"arraybuffer"–ArrayBufferとして取得(バリナリデータに対して, チャプター ArrayBuffer, binary arrays を参照),"blob"–Blobとして取得 (バイナリデータに対して, チャプター Blob を参照),"document"– XML ドキュメントとして取得 (XPath と他の XML メソッドを使うことができます),"json"– JSON として取得 (自動的にパースされます).
例えば、JSON としてレスポンスを取得してみましょう:
let xhr = new XMLHttpRequest();
xhr.open('GET', '/article/xmlhttprequest/example/json');
xhr.responseType = 'json';
xhr.send();
// レスポンスは {"message": "Hello, world!"}
xhr.onload = function() {
let responseObj = xhr.response;
alert(responseObj.message); // Hello, world!
};
昔のスクリプトには、xhr.responseText や xhr.responseXML プロパティがあるかもしれません。
これらは、文字列や XML ドキュメントを取得するために歴史的な理由から存在しています。最近では、xhr.responseType で形式を設定して、上のように xhr.response を取得するべきです。
Ready states
XMLHttpRequest は状況が進むにつれ、状態が変化します。現在の状態は xhr.readyState でアクセスできます。
すべての状態は 仕様 にあります:
UNSENT = 0; // 初期状態
OPENED = 1; // open が呼ばれた
HEADERS_RECEIVED = 2; // レスポンスヘッダを受け取った
LOADING = 3; // レスポンスはロード中
DONE = 4; // リクエスト完了
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(); // リクエストを終了する
これは 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) { // 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のもう一つの特徴はsetRequestHeaderを取り消すことはできないということです。一度ヘッダを設定すると、それが設定されます。さらなる呼び出しはヘッダへの情報の追加であり、上書きでは有りません。
例:
xhr.setRequestHeader('X-Auth', '123'); xhr.setRequestHeader('X-Auth', '456'); // ヘッダはこうなります: // 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に依存しません)。なので、簡単に個々のヘッダに分割することができます。名前と値のセパレータは常にコロンとそれに続くスペースです": "。これは仕様で決められています。なので、name/value のペアをもつオブジェクトを取得したい場合は少し JS が必要になります。
例えばこのようになります(2つのヘッダの名前が同じ場合、前者のヘッダが後者のヘッダで上書きされる想定です):
let headers = xhr .getAllResponseHeaders() .split('\r\n') .reduce((result, current) => { let [name, value] = current.split(': '); result[name] = value; return result; }, {});
POST, FormData
POST リクエストをするには、組み込みの FormData オブジェクトを使います。
構文:
let formData = new FormData([form]); // オブジェクトを作成します。オプションで <form> を指定します
formData.append(name, value); // フィールドを追加します
オプションでフォームから作成し、必要に応じて “追加” フィールドを追加します。その後:
xhr.open('POST', ...)–POSTメソッドを使いますxhr.send(formData)で、フォームをサーバに送信します
例: