HTML Living Standard  第3部 Javascript 12 非同期通信


 

12 非同期処理

同期処理とは、プログラムの記述順に処理を実行する方式です。それに対して非同期処理は、プログラムの記述とは異なる順に実行する方式です。

同期処理は順番に実行してくれて分かりやすいですが、途中に時間がかかる処理があった場合、次の処理を行うことができません。例えば、サーバーと通信しながら画面を表示しているとき、サーバーから応答が返ってくるまで画面が止まってしまっては問題です。そのため、その対策として、非同期処理が行われます。

非同期処理は、次のオブジェクトで実現できます。

fetch
XMLHttpRequest で実現していた非同期のネットワーク通信を簡単にわかりやすく記述できる
Promise
fetch など非同期的に動作する関数が返すオブジェクト。(本来返したい)値を渡せる状態になったら、Promise を通して値を得る
XMLHttpRequest
サーバーと対話し、いろいろな種類のデータをやり取りする

ただし、JavaScript はシングルスレッドで動いていますので、非同期とはいっても同時に2つの処理を実行することはできません。JavaScript では、同期であろうと非同期であろうと2つ以上の処理を並行して行うことはできません。しかし、他(例えば、データベースなど)に処理を任せている間に、その処理の終了を待たないで次の命令を実行できるという意味で非同期なのです。

12.1  非同期通信(fetch)

fetch を使用するにあたって、fetch およびその他の必要なオブジェクトについて説明します。


注意

他のサーバー上のファイルはそのサーバーが許可していない限り読み込むことはできません。取得が許可されるには、サーバー側のレスポンスヘッダの Access-Control-Allow-Origin にアクセスする側の URI を追加してもらう必要があります。


12.1.1  fetch 命令

非同期のネットワーク通信は、XMLHttpRequest によって提供されてきました。それに変わる次の Ajax 仕様として fetch が策定されています。

次の形式で fetch() を呼び出す場合、取得するリソースのパスを第1引数で指定します。呼び出し後、fetch() は Promise インスタンスを返します。fetch() によるリクエストが成功であっても不成功であっても、Promise は解決され、リクエストに対するレスポンスを取得することができます。

fetch() の第2引数は任意で、リクエストの初期化に使用されるオプションを指定します。

なお、Promise については、「12.2 非同期処理(Promise)」 を参照してください。

fetch(url[, options]))

リクエストやレスポンスを操作し非同期のネットワーク通信を行い、その結果を含む Promise インスタンスを生成する。

引数 url読み込むファイルの URL あるいは相対パス などを表す文字列
optionsオプション(JSON 形式のオブジェクト)下記参照
戻り値リクエストに対するレスポンスを持った Promise インスタンス

JSON 形式のオブジェクトは次のようなフォーマットを持ちます。

  • オブジェクト = {メンバー[, メンバー[, メンバー ...]]}
  • メンバー = 名前:値 (名前は "" で囲む必要がある場合がある)
  • 値 = 数値 または "文字列" または オブジェクト

options のメンバーの、名前とその値には次のようなものがあります。

名前説明
methodリクエストするメソッド
GETデータの取得 (規定値)
POSTデータの作成(リクエストの送信)
HEADヘッダの取得
PUTデータの作成・置換
DELETEデータの削除
headersHeaders インスタンス、文字列リクエストに追加したいヘッダー (規定値 {})
bodyBlob、BufferSource、FormData インスタンス などリクエストに追加したいボディ
(method が GET、HEAD のときは使用できない)
modeリクエストで使いたいモード(他サーバー body 部の表示ではなく読み取り)の許可、不可
corsクロスオリジンリクエストの許可
no-cors他サーバーは HEAD 部の読み取りと GET、POST メソッドのみの許可(規定値)
same-originクロスオリジンリクエストの不可(エラーになる)
credentialsrequest に使用したい秘密情報
omitCookieなど認証情報を付与しない
same-origin同一ドメインの場合に認証情報を付与する(規定値)
include全てのドメインで認証情報を付与する
cache使用したいリクエストのキャッシュのモード
defaultキャッシュ内に新しいものがあれば利用する。
なければ読み込んでキャッシュを更新する(規定値)
no-storeキャッシュが全く無いかのように常に読み込む
reloadネットワークでは、常に読み込んでキャッシュを更新する
no-cache常に読み込んでキャッシュを更新する
force-cacheキャッシュ内にあれば古くても利用する。なければ読み込んでキャッシュを更新する
only-if-cacheキャッシュ内にあれば古くても利用する。なければネットワークエラーを返す
redirectリダイレクト
follow自動でリダイレクトに従う(規定値)
errorリダイレクトが起こった場合エラーを伴って中止する
manual手動でリダイレクトを処理する
referrerリクエストのリファラー
URL同じオリジンの URL
about:clientグローバルのデフォルト(規定値)
(空文字列)リファラを送信しない
referrerPolicyリンク先や外部リソースをリクエストする際にリファラ(アクセス元の URL 情報)を送信するか否かを指定する
(空文字列)特にリファラに対して条件指定をせず、ブラウザの挙動による(規定値)
no-referrerリファラを送信しない
no-referrer-when-downgradeHTTPS→HTTP にはリファラを送信しない
same-originリンク元とリンク先が同一オリジンの場合にリファラを送信する(注)
originリンク元のオリジンのみを送信する
origin-when-cross-originリンク元とリンク先が異なるオリジンの場合、リンク元のオリジンのみを送信する
同一オリジンの場合、リンク元の完全な URL をリファラとして送信する
strict-origin-when-cross-originリンク先が HTTPS の場合、origin-when-cross-origin と同じ
unsafe-urlリンク元の完全な URL をリファラとして送信する
integritysha256、sha384、sha512 などから始まる
base64 でエンコードされたハッシュ値
サブリソース完全性
(例:sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)

(注)オリジン:スキーム、ホスト、ポート番号の組み合わせ


記述例
<script>
// promise インスタンスの生成
var promise = fetch("sample.php",  // 実行ファイル
                {                  // オプション
                  method: "post",
                  headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"},
                  body: "name=Mary&gender=female"
                });

// 以下略

</script>

(1) データの取得

データの取得には、リクエストメソッドとして GET を指定します。

ファイルを取得する場合は、自サーバー上のファイルの URL か相対パスで指定します。他のサーバー上のファイルはそのサーバーが許可していない限り読み込むことはできません。

記述例
<span id="d1"></span><br>
<!-- 以下略 -->

<script>
var d1 = document.getElementById("d1");
// 以下略

var promise = fetch("sample.txt", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {        // アロー関数で記述してるが、response => は function(response) と同じ意味
    d1.textContent = response.url;
    d2.textContent = response.type;
    d3.textContent = response.status;
    d4.textContent = response.statusText;
    d5.textContent = response.ok;
    var headers = "";
    response.headers.forEach((value, key) => headers += key + " : " + value + "<br>");
    d6.innerHTML = headers;
  })
  .catch(err => d1.textContent = err);
</script>

ファイルを読み込んで、レスポンスの内容を表示しています。

headers() の返す値はオブジェクトなので、そのメンバーの名前と値をすべて表示しています。

実行例

(1-1) テキストとして受け取る

ファイルの内容は、text() の返す値の中に Promise インスタンスとして格納されています。

記述例
<span id="d1" style="white-space:pre;"></span><br>

<script>
var d1 = document.getElementById("d1");

var promise = fetch("sample.txt", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {        // アロー関数で記述してるが、response => は function(response) と同じ意味
    response.text().then(t => d1.textContent = t);
  })
  .catch(err => d1.textContent = err);
</script>

text() の返す値はファイルの内容を持った Promise インスタンスです。ファイルの内容は表示する span 要素に white-space:pre 属性を指定しているので、改行されてすべて表示されています。

実行例

(1-2) バイナリデータとして受け取る

ファイルの情報は、blob() の返す値の中に Promise インスタンスとして格納されています。しかし、ファイルの内容は、fetch しただけではまだ読み込まれていませんので、この例では FileReader で読み込んでいます。

ただし、FileReader での読み込みは非同期で行われますので、読み込みの完了を loadend イベントを受け取ることによって判断しています。

また、ファイルの内容は reader.result に読み込まれているのですが、reader.result が ArrayBuffer なので直接操作することはできません。そのため、Uint8Array(8 ビット符号なし整数値の配列)に変換しています。

ちなみに blob とは Binary Large Object のことで、一般的に画像や音声などの大きなバイナリデータそのまま格納するためのデータ型です。

記述例
<span id="d1"></span><br>
<!-- 以下略 -->

<script>
var d1 = document.getElementById("d1");
// 以下略

var promise = fetch("sample.png", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {        // アロー関数で記述してるが、response => は function(response) と同じ意味
    response.blob().then(b => {
      d1.textContent = b.type;  // MIME タイプ
      d2.textContent = b.size;  // バイトサイズ
      var reader = new FileReader();
      reader.addEventListener("loadend", function() {
        var data = new Uint8Array(reader.result);
        var x = "";
        for (var i = 0 ; i < 16 ; i++) {           // 最初の16バイトだけ表示
          x += ("0" + data[i].toString(16).toUpperCase()).substr(-2) + " ";  // 16進数化
        }
        d3.textContent = x;
      });
      reader.readAsArrayBuffer(b);  // ファイルを読み込んで ArrayBuffer に格納する
    });
  })
  .catch(err => d1.textContent = err);
</script>

ファイルの内容の最初の16バイトだけを表示しています。

実行例

画像ファイルを読み込んで img 要素に表示することもできます。

記述例
<img id="d1"><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var promise = fetch("sample.png", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => response.blob().then(b => d1.src = URL.createObjectURL(b)))  // 画像の URL を得て、img 要素に設定する
  .catch(err => d2.textContent = err);
</script>

画像が表示されます。

実行例

(1-3) arrayBuffer で受け取る

ファイルの内容は、arrayBuffer() の返す値の中に Promise インスタンスとして格納されています。ただし、 ArrayBuffer なので直接操作することはできません。そのため、Uint8Array(8 ビット符号なし整数値の配列)に変換しています。

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var promise = fetch("sample.png", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {        // アロー関数で記述してるが、response => は function(response) と同じ意味
    response.arrayBuffer().then(b => {
      var data = new Uint8Array(b);
      d1.textContent = data.length;  // バイトサイズ
      var x = "";
      for (var i = 0 ; i < 16 ; i++) {           // 最初の16バイトだけ表示
        x += ("0" + data[i].toString(16).toUpperCase()).substr(-2) + " ";  // 16進数化
      }
      d2.textContent = x;
    });
  })
  .catch(err => d1.textContent = err);
</script>

ファイルの内容の最初の16バイトだけを表示しています。

実行例

画像ファイルを読み込んで img 要素に表示することもできます。

記述例
<img id="d1"><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var promise = fetch("sample.png", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {
          response.arrayBuffer().then(b => {
            var data = new Uint8Array(b);
            var binaryData = "";
            for (var i = 0, len = data.byteLength ; i < len ; i++) {
              binaryData += String.fromCharCode(data[i]);
            }
            d1.src = "data:image/png;base64," + window.btoa(binaryData);  // img 要素に設定する
          });
  })
  .catch(err => d2.textContent = err);
</script>

画像が表示されます。

実行例

(1-4) JSON で受け取る

ファイルの内容は、json() の返す値の中に Promise インスタンスとして格納されています。ただし、 JSON 形式のオブジェクトなので直接表示することはできません。そのため、すべてのキーを取得してそのキーに対応する値を表示する関数(disp)を作成して表示しています。

注意

JSON ファイルを読み込むためには、JSON という拡張子を Web サーバーの MIME に例えば text/plain などのように登録する必要があります。


記述例
<span id="d1" style="white-space:pre;"></span><br>

<script>
const indent = "  ";
const delim = ":";
// オブジェクトの表示
function disp(ind, obj) {
  var keys = Object.keys(obj);    // すべてのキー
  var d = "{\n";
  var leading = indent.repeat(++ind);  // インデント
  keys.forEach((key) => {    // キーを順番に取り出す
    var val = obj[key];      // キーに対応する値
    if (typeof val === 'object') {
      d += leading + key + delim + disp(ind, val);    // オブジェクトがネストしている
    }
    else {
      var quote = (typeof val === 'string') ? "\"" : "";        // 文字列なら "" で囲む
      d += leading + key + delim + quote + val + quote + "\n";  // 値
    }
  });
  leading = indent.repeat(ind-1);        // インデント
  return d + leading + "}\n";
}

var d1 = document.getElementById("d1");

var promise = fetch("sample.json", {method:"get"});        // method:"get" は規定値なので記述しなくても同じ
promise
  .then(response => {        // アロー関数で記述してるが、response => は function(response) と同じ意味
    response.json().then(j => {
      d1.textContent = disp(0, j);
    });
  })
  .catch(err => d1.textContent = err);
</script>

読み込まれた JSON ファイルの内容は j の中にオブジェクトとして読み込まれます。この例ではそのオブジェクトの内容を表示しています。なお、表示する div 要素に white-space:pre 属性を指定しているので、改行されて表示されています。

実行例

(2) データの作成

サーバーにデータを作成するためにパラメーターをリクエストとして送ります。リクエストの送信には、リクエストメソッドとして POST を指定します。

下の例では、サーバーの CGI を呼び出しパラメーターを送信しています。そして、その返答を受け取り表示しています。

(2-1) パラメーターを文字列で渡す

ファイルの内容は、text() の返す値の中に Promise インスタンスとして格納されています。

サーバーにパラメーターを文字列として渡すには次のように行います。

application/x-www-form-urlencoded は、フォームに入力されたデータを送信するのための形式です。データは id=data の形式で、データが複数ある場合は & で区切られます(id1=data1&id2=data2)

記述例
<span id="d1"></span><br>

<script>
var d1 = document.getElementById("d1");

var params = new URLSearchParams();     // サーバーに渡されるパラメーター
params.append("name", "Mary");
params.append("gender", "female");
params.append("fruits", "みかん");
fetch("post.cgi",
    {                // オプション
      method: "post",
      headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"},
      body: params      // name=Mary&gender=female&fruits=みかん
    })
  .then(response => response.text().then(res => d1.innerHTML = res))
  .catch(err => d1.textContent = err);
</script>

「Mary」さんは「女性」で「みかん」が好きだと送っています。実行例はそれに対する返答です。

実行例

なお、上記の例ではパラメーターを URLSearchParams を使用して生成していますが、body のところに直接文字列を記述しても構いません。

      body: "name=Mary&gender=female&fruits=みかん"

(2-2) パラメーターを FormData で渡す

サーバーにパラメーターを FormData として渡すには次のように行います。

FormData は、マルチパートの情報として送られるようで、multipart/form-data の指定が必要です。したがって、サーバー側でもパラメーターの受け取り方法が変わります。

また、「所持品」のようなチェックボックスは、チェックされたところの value をタブで連結した文字列となるようです。

記述例
<form id="favi">
<label>氏名:<input type="text" name="name"></label><br>
性別:
<label><input type="radio" name="gender" value="male"></label>
<label><input type="radio" name="gender" value="female"></label>
<br><br>
所持品:
<label><input type="checkbox" name="prop" value="CellPhone" checked="checked"> スマフォ</label>
<label><input type="checkbox" name="prop" value="Car"> 自動車</label>
<label><input type="checkbox" name="prop" value="Cottage"> 別荘</label>
<br>
<label>好きな果物:
<select name="fruits">
 <option value="りんご" label="りんご"></option>
 <option value="みかん" label="みかん"></option>
 <option value="バナナ" label="バナナ" selected="selected"></option>
 <option value="パイナップル" label="パイナップル"></option>
</select></label>
</form>

<span id="d1">何か入力してください。</span><br>

<script>
var d1 = document.getElementById("d1");

var favi = document.getElementById("favi");
favi.addEventListener("change", send, false);      // 入力欄に変更があったら send() を呼び出す

function send() {
  fetch("post.cgi",
    {                // オプション
      method: "post",
      headers: {"Content-type": "multipart/form-data; charset=UTF-8"},
      body: new FormData(favi)
    })
  .then(response => response.text().then(res => d1.innerHTML = res))
  .catch(err => d1.textContent = err);
}
</script>

下記の欄に適当な入力してください。入力するたび(ただし、「氏名」欄は入力欄から抜けたとき)に返答が変わります。

実行例

(3) ファイルの送信

<input type="file"> で指定されたファイルをアップロードしサーバーに保存します。この例では、ファイルが指定された時点で自動的に送信され、サーバーに保存されます。そして、その保存されたファイル名を受け取り、img タグの src 属性に設定することによって、その画像を表示しています。

記述例
<form id="up">
<label>ファイル:<input type="file" name="file" type="file" accept="image/*"></label><br>
</form>

<img id="d1" style="max-height:200px;max-width:200px;"><br>
<div id="d2" style="color:crimson"></div>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var favi = document.getElementById("up");
favi.addEventListener("change", send, false);      // 入力欄に変更があったら send() を呼び出す

function send() {
  fetch("upload.cgi",
    {                // オプション
      method: "post",
      body: new FormData(favi)
    })
  .then(response => {
    response.text().then(res => {
      if (response.ok) {
        d1.src = res;           // 画像 URL
        d2.textContent = "";
      }
      else {
        d1.src = "";
        d2.innerHTML = res;     // エラーメッセージ
      }
    })
  })  
  .catch(err => d2.textContent = err);
}
</script>

画像ファイルを選択すると自動的にアップロードされます。また、ファイルを指定するたびにアップロードされた画像が(大きな画像は縮小されて)表示されます。

アップロードする画像ファイルのサイズは 180KB 以下のものを選んでください。

実行例

(4) ヘッダの取得

ヘッダの取得には、リクエストメソッドとして HEAD を指定します。

GET メソッドでも同じように、ヘッダを取得することができますが、GET メソッドはデータのボディ部のデータも一緒に取得してしまうので、データの更新日時(last-modified)などのヘッダー情報のみを取得したい場合は、通信の無駄が発生してしまいます。

記述例
<span id="d1"></span><br>

<script>
var d1 = document.getElementById("d1");

fetch("sample.txt", {method:"head"})
  .then(response => response.headers)
  .then(headers => {
    var headerContents = "";
    headers.forEach((value, key) => headerContents += key + " : " + value + "<br>");
    d1.innerHTML = headerContents;
  })
  .catch(err => d1.textContent = err);
</script>

ファイルを読み込んで、ヘッダー情報を表示しています。

headers() の返す値はオブジェクトなので、そのメンバーの名前と値をすべて表示しています。

実行例



12.1.2  Request オブジェクト

サーバーへの要求内容を設定します。

fetch の引数としても指定できます。

(1) インスタンス生成

new Request(url [, options])

Request インスタンスを生成する。

引数 url:読み込むファイルの URL あるいは相対パス などを表す文字列

引数 options:オプション(JSON 形式のオブジェクト) fetch 参照

戻り値:Request インスタンス


(2) プロパティ

Request インスタンスの主なプロパティです。

プロパティ説明
methodR/Oリクエストするメソッド
GETデータの取得
POSTデータの作成(リクエストの送信)
HEADヘッダの取得
PUTデータの作成・置換
DELETEデータの削除
urlR/OURLリクエストの URL
headersR/OHeaders インスタンスリクエストの Headers インスタンス
referrerR/Oリファラ該当ページに遷移する直前に閲覧されていた遷移元の URL
URL同じオリジンの URL
about:clientグローバルのデフォルト
(空文字列)リファラを送信しない
referrerPolicyR/Oポリシーリファラに関するポリシー××××××
(空文字列)特にリファラに対して条件指定をせず、ブラウザの挙動による
no-referrerリファラを送信しない
no-referrer-when-downgradeHTTPS→HTTP にはリファラを送信しない
same-originリンク元とリンク先が同一オリジンの場合にリファラを送信する(注)
originリンク元のオリジンのみを送信する
origin-when-cross-originリンク元とリンク先が異なるオリジンの場合、リンク元のオリジンのみを送信する
同一オリジンの場合、リンク元の完全な URL をリファラとして送信する
strict-origin-when-cross-originリンク先が HTTPS の場合、origin-when-cross-origin と同じ
unsafe-urlリンク元の完全な URL をリファラとして送信する
modeR/Oリクエストのモード
corsクロスオリジンリクエストの許可
no-cors他サーバーは HEAD 部の読み取りと GET、POST メソッドのみの許可
same-originクロスオリジンリクエストの不可(エラーになる)
credentialsR/Orequest に使用したい秘密情報
omitCookieなど認証情報を付与しない
same-origin同一ドメインの場合に認証情報を付与する
include全てのドメインで認証情報を付与する
integrityR/Obase64 でエンコードされたハッシュ値リクエストの サブリソース完全性を示す値××××××
cacheR/Oリクエストのキャッシュモード
defaultキャッシュ内に新しいものがあれば利用する。
なければ読み込んでキャッシュを更新する
no-storeキャッシュが全く無いかのように常に読み込む
reloadネットワークでは、常に読み込んでキャッシュを更新する
no-cache常に読み込んでキャッシュを更新する
force-cacheキャッシュ内にあれば古くても利用する。なければ読み込んでキャッシュを更新する
only-if-cacheキャッシュ内にあれば古くても利用する。なければネットワークエラーを返す
bodyUsedR/OBoolean 値ボディが既に読み取られた(true)
まだ読み取られていない(false)

(注)オリジン:スキーム、ホスト、ポート番号の組み合わせ


記述例
<span id="d01"></span><br>
<!-- 以下略 -->

<script>
var d01 = document.getElementById("d01");
// 以下略

var request = new Request("sample.php",  // 実行ファイル
                {                        // オプション
                  method: "post",
                  headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"},
                  body: "name=Mary&gender=female"
                });
d01.textContent = request.method;
d02.textContent = request.url;
var headers = "";
request.headers.forEach(function(value, key) {headers += key + " : " + value + "<br>"});
d03.innerHTML = headers;
d04.textContent = request.referrer;
d05.textContent = request.referrerPolicy;
d06.textContent = request.mode;
d07.textContent = request.credentials;
d08.textContent = request.integrity;
d09.textContent = request.cache;
d10.textContent = request.bodyUsed;
</script>
実行例

12.1.3  Response オブジェクト

サーバーからの返信内容を含んでいます。

(1) インスタンス生成

new Response([body [, options]])

Request インスタンスを生成する。

引数 body:返信内容を表す Blob、FormData、URLSearchParams、文字列 など

引数 options:オプション(JSON 形式のオブジェクト) 下記参照

戻り値:Request インスタンス

options には次のようなメンバーがあります。

名前説明
status200 OK
404 Not Found
501 Not Implemented など
詳しくは こちら
HTTP ステータスコード
statusTextOK、Not Found、Not Implemented などステータスコードに対応したステータスメッセージ
headersHeaders インスタンスレスポンスに関連した Headers インスタンス

(2) プロパティ

Response インスタンスの主なプロパティです。

プロパティ説明
headersR/OHeaders インスタンスレスポンスの Headers インスタンス
okR/O論理値リクエストが成功(status が 200~299)したら true
redirectedR/O論理値リクエストがリダイレクトされたら true
statusR/OステータスコードHTTP ステータスコード(詳しくは こちら
statusTextR/O文字列ステータスコードに対応したステータスメッセージ
typeR/Oレスポンスタイプ
default正常(同一サーバーからのレスポンス)
cors他サーバーからのレスポンス
errorネットワークエラー
opaque他サーバーへの "no-cors"(HEAD 部の読み取りと GET、POST メソッドのみ)でのレスポンス
urlR/OURLレスポンス URL
bodyUsedR/O論理値response でボディが使われたかどうかを示す Boolean 値

Response インスタンスは、fetch が返してくる情報として使用されることが多いです。fetch は Promise インスタンスを返しますが、Response インスタンスはその中に含まれています。

記述例
<span id="d01"></span><br>
<!-- 以下略 -->

<script>
var d01 = document.getElementById("d01");
// 以下略

var response = new Response("ABCDEF",  // 返信内容
                {                        // オプション
                  status: 300,
                  statusText: "NG",
                  headers: {"Content-type": "application/x-www-form-urlencoded; charset=UTF-8"}
                });
var headers = "";
response.headers.forEach(function(value, key) {headers += key + " : " + value + "<br>"});
d01.innerHTML = headers;
d02.textContent = response.ok;
d03.textContent = response.redirected;
d04.textContent = response.status;
d05.textContent = response.statusText;
d06.textContent = response.type;
d07.textContent = response.url;
d08.textContent = response.bodyUsed;
</script>

この例では、status に 300 を設定していますので、異常終了したということで ok は false になります。

実行例

(3) メソッド

メソッド引数機能戻り値
clone()なしResponse オブジェクトのクローンを生成するResponse
error()なしネットワークエラーに関連した新しい Response オブジェクトを返すResponse
redirect()なし異なる URL で新しい Response オブジェクト を生成するResponse

Response オブジェクトに含まれる内容は ArrayBuffer、Blob、JSON、プレーンテキストのいずれかの形で受け取ることが出来ます。それらを読み込むには次のメソッドを使用します。

メソッド引数機能戻り値
arrayBuffer()なしボディテキストを ArrayBuffer として解析した結果で解決される Promise を返すPromise
blob()なしボディテキストを Blob として解析した結果で解決される Promise を返すPromise
formData()なしボディテキストを FormData として解析した結果で解決される Promise を返すPromise
json()なしボディテキストを Json として解析した結果で解決される Promise を返すPromise
text()なしボディテキストを文字列として解析した結果で解決される Promise を返すPromise

text() の例です。

記述例
<span id="d1" style="white-space:pre;"></span><br>

<script>
var d1 = document.getElementById("d1");

var promise = fetch("sample.txt");
promise.then(function(response) {
  response.text().then(function(t) {d1.textContent = t});
});
</script>

text() の返す値はファイルの内容を持った Promise インスタンスです。ファイルの内容は表示する div 要素に white-space:pre 属性を指定しているので、改行されてすべて表示されています。

実行例

その他の使用例は、12.1.1 fetch 命令 (1) データの取得 を参照してください。


12.1.4  Headers オブジェクト

Headers インスタンスは、ヘッダ名と値のペアで構成されるリストを持っています。Headers インスタンス自体は、Request インスタンスや Response インスタンスにも含まれますが、そのヘッダ名の構成は異なります。また、ヘッダ名は大文字小文字を区別しません。

Headers オブジェクトは、Map オブジェクトと似たメソッドを持っており、ヘッダ名と値のペアを自由に追加・削除することができます。

(1) メソッド

Headers インスタンスは、Map オブジェクトに似た次のようなメソッドを持っています。詳しくは、9.6 連想配列(Map) を参照してください。

メソッド引数機能戻り値
append(name,value)ヘッダ名、値ヘッダに新しいヘッダ名を追加するなし
delete(name)ヘッダ名ヘッダからヘッダ名を削除するなし
entries()なしヘッダ名と値のペアを含むリストを取得するIterator インスタンス
forEach(callback[,thisObject])処理関数、this として使用するインスタンス与えられた関数を配列のすべての要素に対して実行するなし
get(name)ヘッダ名ヘッダからヘッダ名に対応する値を取得する値(文字列)
has(name)ヘッダ名ヘッダにヘッダ名があるかどうかを検査するあれば true、なければ false
keys()なしヘッダに含まれるすべてのヘッダ名のリストを取得するIterator インスタンス
set(name,value)ヘッダ名、値ヘッダ名に対応する値を置き換えるなし
values()なしヘッダに含まれるすべての値のリストを取得するIterator インスタンス

(2) プロパティ(Response インスタンスに含まれる Headers インスタンス)

Headers インスタンスは、ヘッダ名と値のペアで構成されるリストを持っていますが、その内容はインスタンスごとに異なります。

例えば、Response インスタンスに含まれる Headers インスタンスには次のようなプロパティが含まれています。

プロパティ
accept-rangesR/Oターゲットリソースに対する範囲要請(bytes:バイト単位での範囲要請をサポート)
content-lengthR/Oコンテンツのサイズ(単位はバイト)
content-typeR/Oコンテンツのアプリケーション・メディアタイプ(MIME と同じ)
dateR/O日付
etagR/Oエンティティタグ。リソースの全体や一部を特定する固有値
last-modifiedR/Oコンテンツの最終更新時刻
serverR/OHTTPサーバアプリケーション種類を示す固有テキスト値

新規に生成しプロパティを個別に追加した Headers インスタンスと、Response インスタンスに含まれる Headers インスタンスの両方について、プロパティのヘッダ名と値のペアを表示しています。

記述例
<script>
<span id="d01"></span><br>
<span id="d02"></span><br>

<script>
var d01 = document.getElementById("d01");
var d02 = document.getElementById("d02");

var headersObj = new Headers();
headersObj.append("String", "ABC");
headersObj.append("Number", 12);
var headers = "";
headersObj.forEach(function(value, key) {headers += key + " : " + value + "<br>"});
d01.innerHTML = headers;

fetch("sample.txt", {method:"get"}).then(function(response) {
    var headers = "";
    response.headers.forEach(function(value, key) {headers += key + " : " + value + "<br>"});
    d02.innerHTML = headers;
  })
  .catch(err => d02.textContent = err);
</script>

ヘッダ名は大文字で指定しても小文字で登録されるようです。

実行例


12.2  非同期処理(Promise)

非同期処理の最終的な完了処理(もしくは失敗)およびその結果の値を表現します。

Promise の基本的な考え方は「非同期的に動作する関数は、受信したデータやファイルから読み込んだデータなどの、本来返したい戻り値の代わりに『Promise』という特別なオブジェクトを返しておき、(本来返したい)値を渡せる状態になったら、その Promise オブジェクトを通して呼び出し元に値を渡す」というものです。

非同期的に動作する代表的な関数が fetch です。

12.2.1  Promise インスタンス

Promise インスタンスの持つメソッドについて説明します。

(1)インスタンス生成

新規の Promise インスタンスを生成します。

Promise インスタンスの状態は以下のいずれかになります。

  • pending : 初期状態。成功も失敗もしていない
  • fulfilled : 処理が成功して完了した(resolve 関数を実行した)
  • rejected : 処理が失敗した(reject 関数を実行した)

new Promise(function(resolve[, reject]))

Promise インスタンスを生成する。

引数 2つの関数 resolve と reject を引数とする関数

    (この関数は非同期の処理を開始して、成功した場合は resolve を、失敗した場合は reject を呼び出すようにする)


例えば、XMLHttpRequest を使用してファイルを読み込む処理を考えてみます。XMLHttpRequest はファイルの読み込みを非同期に行いますので、ファイル読み込みの完了はイベント(この例では readystatechange)によって知らされます。

次の例は、ファイル xxxxx.txt を読み込んで、その文字数を表示するものです。

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>
<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

class MyFileReader {
  constructor(filename) {
    this.filename = filename;
    this.callback = null;
    this.length = 0;
    this.req = new XMLHttpRequest();  // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
    this.req.open("get", this.filename, true); // アクセスするファイルを指定
  }
  read(callback) {
    var self = this;
    this.callback = callback;
    this.req.send(null);                  // HTTPリクエストの発行

    this.req.onreadystatechange = function(){
      if (self.req.readyState == 4) {
        if (self.req.status == 200) {
          self.length = self.req.responseText.length;   // ファイルサイズ
          self.callback();
        }
        else if (self.req.status == 404) {
          throw self.filename + " not found.";
        }
      }
    }
  }
  size() {
    return this.length;
  }
}

function execute() {
  d2.textContent = reader.size();   // 文字数 ==> d2  -----②
}

var filename = "xxxxx.txt";
var reader = new MyFileReader(fileName);
reader.read(execute);               // ファイルを読み込む
d1.textContent = reader.size();     // 文字数 ==> d1  -----①
</script>

XMLHttpRequest を使用した場合、ファイルは一度にすべて読み込まれ、responseText に保存されます。したがって、d1(①のところ)には読み込まれた文字数が設定されてもよさそうですが、実行結果としては 0 になっています。これは、ファイルの読み込みが非同期で行われているため、ファイルを読み込み終わる前に ① の命令が実行されてしまうからです。

これに対して、ファイルの読み込みが完了したときに(read からコールバックされる execute 内で)設定している d2(②のところ)には読み込んだ文字数が設定されています。

実行例

それでは同じ処理を Promise を使用して記述してみます。

resolve の引数は返したい値、reject の引数はエラー理由を設定した、文字列や数値や Error あるいはそのサブクラスのインスタンスです。

記述例
<span id="d1"></span><br>
<script>
var d1 = document.getElementById("d1");

class MyFileReader {
  constructor(filename){
    return new Promise(function(resolve, reject){
      var req = new XMLHttpRequest();  // HTTPでファイルを読み込むための XMLHttpRrequest インスタンスを生成
      req.open("get", filename, true); // アクセスするファイルを指定
      req.addEventListener("loadend", function(e){
        // loadend イベントはリクエストが成功した場合も失敗した場合も呼び出される
        if (req.readyState == 4 && req.status == 200) {
          resolve(req.responseText);               // fulfilled
          return;    // return はなくてもよい(この後 reject を実行しても fulfilled の状態は変わらない)
        }
        reject(new Error("error"));                // rejected
      });
      req.send(null);
    });
  }
}

var filename = "xxxxx.txt";
var promise = new MyFileReader(filename);
promise.then(
  function(text){d1.textContent = text.length;},  //成功した場合(文字数)     -----①
  function(err){d1.textContent = err.message;}    //失敗した場合
);
</script>

MyFileReader は、ファイルの読み込みが正常に終了したのならば resolve を呼び、読み込んだファイルの内容を返し、そして、失敗したのならば reject を呼び、"error" という文字列を持つ Error インスタンスを返す Promise インスタンスを生成し、戻り値としています。

そして、これらの値が then の引数として指定された関数に渡されます。成功したときが一つ目の関数、失敗したときが二つ目の関数です。

実行例

(2)then メソッド

then() メソッドは Promise を返します。最大2つの引数、Promise が成功した場合と失敗した場合のコールバック関数を取ります。

メソッド引数機能戻り値
then(func1[, func2])Promise を返す関数Promise インスタンスに実装した関数の成否によって処理を行う。成功した場合一つ目の関数、失敗した場合は二つ目の関数が実行される。Promise インスタンス

先の例と同じものです。ここですでに then が使用されていました。

この例では MyFileReader が成功すれば resolve を呼び出し(─→ ① のところ)、その引数の req.responseText の内容が then の一つ目の関数(←── ① のところ)の text に設定され、一つ目の関数が実行されます。

もし失敗すれば reject を呼び出し(─→ ② のところ)、その引数の Error インスタンスが then の二つ目の関数(←── ② のところ)の err に設定され、二つ目の関数が実行されます。

記述例
<span id="d1"></span><br>
<script>
var d1 = document.getElementById("d1");

class MyFileReader {
  constructor(filename){
    return new Promise(function(resolve, reject){
      var req = new XMLHttpRequest();  // HTTPでファイルを読み込むための XMLHttpRrequest インスタンスを生成
      req.open("get", filename, true);
      req.addEventListener("loadend", function(e){
        if (req.readyState == 4 && req.status == 200) {
          resolve(req.responseText);      //────────→ ①
          return;
        }
        reject(new Error("error"));       //────────→ ②
      });
      req.send(null);
    });
  }
}

var filename = "xxxxx.txt";
var promise = new MyFileReader(filename);
promise.then(
  function(text){d1.textContent = text.length;},  //←── ① (成功したとき)
  function(err){d1.textContent = err.message;}    //←── ② (失敗したとき)
);

</script>

then は Promise インスタンスを返すので、then をいくつも続けて書くことができます。このとき、then に記述した関数で値を返すと、その値が次の then に記述した関数の引数になります。なお、値を返さなければ undefined が引数値になります。

記述例
<span id="d1"></span><br>
<script>
var d1 = document.getElementById("d1");

var promise = Promise.resolve();     // new Promise(function(resolve) {resolve();}) と同じ意味
promise
  .then(function(){return "one";})
  .then(function(arg){return arg + " two";})        // arg = "one"
  .then(function(arg){d1.textContent = arg;});      // arg = "one two"
</script>

戻り値が次の関数に引数として渡されます。

実行例

then の中で Promise を返す場合は、次のようになります。

記述例
<span id="d1"></span><br>
<script>
var d1 = document.getElementById("d1");

var promise = Promise.resolve();     // new Promise(function(resolve) {resolve();}) と同じ意味
promise
  .then(function(){return new Promise(function(resolve, reject){resolve("one")});})
  .then(function(arg){return new Promise(function(resolve, reject){resolve(arg + " two")});})   // arg = "one"
  .then(function(arg){d1.style.color="blue"; d1.textContent = arg;});                           // arg = "one two"
  .catch(function(err){d1.style.color="red"; d1.textContent = err;});      // 実行されない
</script>
実行例

(3)catch メソッド

catch() メソッドは Promise を返し、then の途中で失敗した場合のコールバック関数を取ります。

メソッド引数機能戻り値
catch(func)Promise を返す関数Promise インスタンスに実装した関数が失敗したときの戻り値を得るために、引数で指定した関数が実行される。
then(undefined, func) と同じ
Promise インスタンス

then の途中で失敗した場合は、catch で指定した関数が実行されます。

記述例
<span id="d1"></span><br>
<script>
var d1 = document.getElementById("d1");

var promise = Promise.resolve();     // new Promise(function(resolve) {resolve();}) と同じ意味
promise
  .then(function(){return new Promise(function(resolve, reject){resolve("one")});})
  .then(function(arg){return new Promise(function(resolve, reject){reject("error")});})   // 失敗
  .then(function(arg){d1.style.color="blue"; d1.textContent = arg;});      // 実行されない
  .catch(function(err){d1.style.color="red"; d1.textContent = err;});
</script>
実行例

12.2.2  Promise オブジェクト

Promise オブジェクトの持つメソッドについて説明します。

(1)all メソッド

all メソッドは、引数に指定した全ての Promise を実行します。そして、全ての Promise が成功した(fulfilled)か、または1つでも失敗した(rejected)場合に処理が終了します。

メソッド引数機能戻り値
all(list)配列、リストすべてを並列処理し、すべての結果を返す。なし

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var p1 = new Promise(function(resolve) {setTimeout(resolve, 4000, "one");});   // 4秒後に resolve("one") を実行
var p2 = new Promise(function(resolve) {setTimeout(resolve, 1000, "two");});   // 1秒後に resolve("two") を実行
var p3 = new Promise(function(resolve) {setTimeout(resolve, 3000, "three");}); // 3秒後に resolve("three") を実行
var px = new Promise(function(resolve, reject) {setTimeout(reject, 2000, "error");}); // 2秒後に reject("error") を実行

Promise.all([p1, p2, p3]).then(                     // すべて成功  ---①
    function(value) {d1.textContent = value;},     // 成功したときに実行される関数  --- ①-1
    function(reason) {d1.textContent = reason;});
Promise.all([p1, p2, px]).then(                     // 失敗     ---②
    function(value) {d2.textContent = value;},
    function(reason) {d2.textContent = reason;});  // 失敗したときに実行される関数  --- ②-1
</script>

Promise.all は、配列内で指定された関数が並列で実行されます。そして、すべてが成功する ① の場合だと4秒後に then の一つ目の引数で指定された関数(①-1)が実行されます。また、px が失敗する ② の場合では2秒後に二つ目の引数で指定された関数(②-1)が実行されます。

実行例
再実行



(2)race メソッド

race メソッドは、最初に成功(fulfilled)もしくは失敗(rejected)した promise の返した値を引数に、resolve を実行します。

メソッド引数機能戻り値
race(list)配列、リスト並列処理を行い、どれかが成功か失敗したら、その結果を返す。なし

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var p1 = new Promise(function(resolve) {setTimeout(resolve, 4000, "one");});   // 4秒後に resolve("one") を実行
var p2 = new Promise(function(resolve) {setTimeout(resolve, 1000, "two");});   // 1秒後に resolve("two") を実行
var p3 = new Promise(function(resolve) {setTimeout(resolve, 3000, "three");}); // 3秒後に resolve("three") を実行
var px = new Promise(function(resolve, reject) {setTimeout(reject, 2000, "error");}); // 2秒後に reject("error") を実行

Promise.race([p1, p2, p3]).then(                    // 最初に p2 が成功  ---①
    function(value) {d1.textContent = value;},    // 成功が最初の結果だったときに実行される関数 ---①-1
    function(reason) {d1.textContent = reason;});
Promise.race([p1, p3, px]).then(                // 最初に px が失敗  ---②
    function(value) {d2.textContent = value;},
    function(reason) {d2.textContent = reason;}); // 失敗が最初の結果だったときに実行される関数 ---②-1
</script>

Promise.race は、配列内で指定された関数が並列で実行されます。そして、先にどれか(①の例だと1秒後に p2)が成功し、即座に then の一つ目の引数で指定された関数(①-1)が実行されます。また、先にどれか(②の例だと2秒後に px)が失敗した場合には即座に then の二つ目の引数で指定された関数(②-1)が実行されます。

実行例
再実行



(3)resolve メソッド

resolve メソッドは引数で与えられた値で解決(fulfilled)された Promise オブジェクトを返します。

メソッド引数機能戻り値
resolve([value])文字列、Promise など引数で与えられた値で完了した Promise インスタンスを返す。Promise インスタンス

Promise.resolve() は次のように記述したものと同じ意味になります。

new Promise(function(resolve){resolve("success");});           // Promise.resolve("success"))
記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var promise = Promise.resolve("success");      // 成功

promise.then(
  function(value) {d1.textContent = "resolve " + value;},  // 成功だったときに実行される関数
  function(value) {d2.textContent = "reject " + value;}
);
</script>
実行例


(4)reject メソッド

reject メソッドは、引数で与えられた理由で失敗(rejected)した Promise オブジェクトを返します。

メソッド引数機能戻り値
reject([reason])文字列、Error など引数で与えられた値を理由に失敗した Promise インスタンスを返す。Promise インスタンス

Promise.reject() は、次のように記述したものと同じ意味になります。

new Promise(function(resolve, reject){reject("fail");});       // Promise.reject("fail"))
記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
var d1 = document.getElementById("d1");
var d2 = document.getElementById("d2");

var promise = Promise.reject("fail");      // 失敗

promise.then(
  function(value) {d1.textContent = "resolve " + value;},
  function(value) {d2.textContent = "reject " + value;}    // 失敗だったときに実行される関数
);
</script>
実行例


12.3  async、await

async、await は、Promise による非同期処理をより簡潔に効率よく記述できます。

12.3.1  async

async 宣言は、Promise オブジェクトを返す 非同期関数 を定義します。async を関数の前につけると、その関数は Promise を返すようになるわけです。

つまり、async を付けた関数(以降、async 関数という)の戻り値に対して then() で結果を受け取ることができ、catch() でエラー処理を行うことができるようになるわけです。

async function 関数名([引数[, 引数, ...])

Promise を返す非同期関数を定義する。

戻り値:処理が成功して完了した(fulfilled)Promise


async 関数の戻り値は、処理が成功して完了した Promise なので、then() に記述された関数の引数として値を受け取ることができます。

記述例
<div id="d1">0</div>
<div id="d2"></div>

<script>
async function asyncTwice(value) {
  return value * 2;
}

function twice(value) {
  asyncTwice(value).then(function(arg) { 
    d1.textContent = arg;             // ① 非同期に動作しているが、asyncTwice が完了してから代入される
  });
  d2.textContent = d1.textContent;    // ② 非同期に動作しているので、d1 に代入される前に実行される
}

twice(5);
</script>

asyncTwice は非同期に動くので、asyncTwice が終了する前に次の命令である d1 から d2 への代入が行われてしまいます。そのため、最初から d1 に設定されていた 0 が d2 に入ることになります。

実行例


上記の例で定義した asyncTwice は Promise を利用すると次のようになります。

記述例
function asyncTwice(value) {
  return new Promise(function(resolve) { resolve(value * 2) });
}

12.3.2  await

async 関数は、await 式を含むことができます。await 式は、async 関数の実行を一時停止し、 Promise の解決を待ちます。そして async 関数の実行を再開し、解決された値を返します。

await 式

式が Promise オブジェクトのときは、Promise が確定しその結果を返すまで、JavaScript を待機する。式が Promise オブジェクトではない場合はその値自体を返す。

await は async 関数の中でのみ使用できる。

戻り値:解決された Promise オブジェクト の値


await 宣言は、async 関数の中でのみ有効です。async 関数の外で使用した場合はエラーになります。

async 関数の中では await を使うことで .then() を代用できます。

記述例
<div id="d1">0</div>
<div id="d2"></div>

<script>
async function asyncTwice(value) {
  return value * 2;
}

async function twice(value) {
  d1.textContent = await asyncTwice(value);   // ① 非同期に動作しているが、asyncTwice が完了してから代入される
  d2.textContent = d1.textContent;             // ② asyncTwice の戻り値が d1 に代入された後に実行される
}

twice(5);
</script>

asyncTwice は非同期に動きますが、await が記述されていますので、代入は asyncTwice が終了するのを待ってから行われます。

実行例


fetch を使用した例は次のようになります。

記述例
<div id="d1">0</div>
<div id="d2"></div>

<script>
async function readText(filename) {
  let response = await fetch(filename);
  if (response.ok)
    return await response.text();
  else
    return response.status;
}

async function read(filename) {
  d1.textContent = await readText(filename);  // ① 非同期に動作しているが、readText が完了してから代入される
  d2.textContent = d1.textContent;             // ② readText の戻り値が d1 に代入された後に実行される
}

read("sample.txt");
</script>

実行例


なお、Javascript には、一定時間処理を停止するような関数が存在しません。

処理を一時停止させたり、待ちを発生させたい場合に、よく行う方法としては、setTimeout 関数を利用する方法があります。

async、await を使用すると次の例のようになります。

記述例
<div id="d"></div>

<script>
function sleep(msec) {
  return new Promise(function(resolve) {
      setTimeout(resolve, msec);
  });
}
async function countDown(n) {
  d.textContent = n;
  while(n > 0) {
    await sleep(1000);
    d.textContent = --n;
  }
}
countDown(10);
</script>

実行例
再実行



次の例は、1秒ごとに文字を表示する(はずの)disp 関数です。

しかし、Array オブジェクトの forEach や map では、1秒ごとに表示されるのではなく、一度に表示されてしまいます。

記述例
let list = ['a', 'b', 'c', 'd', 'e'];
function disp() {
  list.forEach(async function(v) {
    await sleep(1000);
    d.textContent += v;
  });
}
disp();

実行例
再実行



これは、forEach で関数が5回あっという間に呼ばれ、呼ばれた関数それぞれが1秒待って文字を表示するからで、結果として1秒後にすべての文字が一度に表示されることになるからです。

for 文にすれば、1秒ごとに表示されます。

記述例
let list = ['a', 'b', 'c', 'd', 'e'];
async function disp() {
  for (let i in list) {
    await sleep(1000);
    d.textContent += list[i];
  });
}
disp();

実行例
再実行



12.4  HTTP通信(XMLHttpRequest)

ブラウザ上でサーバーとHTTP通信を行うためのAPIです。

ページ全体を更新する必要なしに、データを受け取ることができます。これでユーザーの作業を中断させることなく、ウェブページの一部を更新することができます。XMLHttpRequest は AJAX プログラミングで頻繁に使用されます。

XMLHttpRequest という名前ではありますが、XML だけでなくあらゆる種類のデータを受け取るために使用することができ、HTTP 以外の file や ftp のプロトコルにも対応しています。

12.4.1  XMLHttpRequest インスタンス

HTTP リクエストを送るには、XMLHttpRequest オブジェクトを作成し、URL を開いてリクエストを送信します。トランザクションが完了すると、インスタンスには結果の HTTP ステータスコードやレスポンスの本文などの有益な情報が格納されます。

(1)インスタンス生成

新規の XMLHttpRequest インスタンスを生成します。

new XMLHttpRequest()

XMLHttpRequest インスタンスを生成する。

引数 なし


記述例
<script>
var req = new XMLHttpRequest();  // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
</script>


(2)インスタンス・プロパティ

プロパティ説明
readyStateR/Oリクエストの状態
UNSENT (0)クライアントは作成済み。open() はまだ呼ばれていない。
OPENED (1)open() が呼び出し済み。
HEADERS_RECEIVED (2)send() が呼び出し済みで、ヘッダーとステータスが利用可能。
LOADING (3)ダウンロード中。responseText には部分データが入っている。
DONE (4)操作が完了した。
responseR/O文字列レスポンスの内容。リクエストが完了していない場合は null(responseType の値と、内容が一致しない場合、nullを返すことがある)
responseTextR/O文字列レスポンスの内容
responseTypeレスポンス型
""型は自動判別される規定値
"arraybuffer"型は ArrayBuffer
"blob"型は Blob(Binary Large Object)
一般的に画像や音声などの大きなバイナリデータそのまま格納するためのデータ型)
"document"型は Document(HTML や XML と解釈できない場合は null)
"json"型は JSON(JSON と解釈できない場合は null)
"text"型は 文字列
responseURLR/OURLレスポンスがあったURL
responseXMLR/OXMLDocument インスタンスレスポンスの内容
statusR/O数値ステータス番号(例えば "Not Found" は "404"、"OK" は "200")
詳しくは こちら
statusTextR/O文字列ステータスのメッセージ(例えば "Not Found"、"OK")
timeoutミリ秒リクエストが自動的に終了するまでの時間(規定値:0)
タイムアウトとなった場合、timeoutイベントが発生する。
uploadR/OXMLHttpRequestUpload インスタンスアップロード状況
withCredentialsR/O論理値リクエストにcookieなどの認証情報を含める(true)か否か(false)

readyState の状態が変わるごとに readystatechange イベントが発生しプロパティが変化します。

次の例では5回呼び出されていて、その時のプロパティ値を表示しています。

記述例
<span id="d11"></span><span id="d12"></span> <!-- 略 --> <br>
<!-- 以下略 -->

<script>
var req = new XMLHttpRequest();  // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
var m = 1;
req.onreadystatechange = function() {
  document.getElementById("d1" + m).textContent = req.status;
  document.getElementById("d2" + m).textContent = req.statusText;
  document.getElementById("d3" + m).textContent = req.readyState;
  document.getElementById("d4" + m).textContent = req.response;
  document.getElementById("d5" + m).textContent = req.responseText;
  document.getElementById("d6" + m).textContent = req.responseType;
  document.getElementById("d7" + m).textContent = req.responseURL;
  document.getElementById("d8" + m).textContent = req.responseXML;
  m++;
};
req.onreadystatechange();
req.open("get", "sample.txt", true); // アクセスするファイルを指定
req.responseType = "";
req.send();
</script>
実行例

受診内容は responseType によって次のように異なります。

記述例
<span id="d10"></span><span id="d11"></span> <!-- 略 --> <br>
<!-- 以下略 -->

<script>
var m = 0;
var req = new XMLHttpRequest();  // HTTPでファイルを読み込むためのXMLHttpRrequestオブジェクトを生成
req.onreadystatechange = function() {
  if (req.readyState != 4) return;
  document.getElementById("d3" + m).textContent = req.responseType;
  document.getElementById("d4" + m).textContent = req.responseURL;
  switch(req.responseType) {
    case "arraybuffer":
     document.getElementById("d1" + m).innerHTML = req.response.byteLength;
     break;
    case "blob":
     document.getElementById("d1" + m).innerHTML = req.response.type + req.response.size;
     break;
    case "document":
     document.getElementById("d1" + m).innerHTML = req.response.contentType;
     document.getElementById("d5" + m).textContent = req.responseXML.documentElement.outerHTML;
     break;
    case "json":
     document.getElementById("d1" + m).innerHTML = dispJSON(req.response);
     break;
    case "text":
     document.getElementById("d1" + m).textContent = req.response;
     document.getElementById("d2" + m).textContent = req.responseText;
     break;
  }
  if (++m < func.length)
    func[m]();
};
var func = [func1, func2, func3, func4, func5];
func[m]();
function func1() {
  req.open("get", "data/sample.png", true);
  req.responseType = "arraybuffer";
  req.send();
}
function func2() {
  req.open("get", "data/sample.png", true);
  req.responseType = "blob";
  req.send();
}
function func3() {
  req.open("get", "data/sample.xml", true);
  req.responseType = "document";
  req.send();
}
function func4() {
  req.open("get", "data/sample.json", true);
  req.responseType = "json";
  req.send();
}
function func5() {
  req.open("get", "data/sample.txt", true);
  req.responseType = "text";
  req.send();
}

function dispJSON(obj) {
  var s = "";
  var k = Object.keys(obj);
  for (var i in k) {
    s += k[i] + ":" + obj[k[i]] + "<br>";
  }
  return s;
}
</script>

response は responseType によって次のように異なります。text 以外はオブジェクトが返ってきますので、この例ではその一部やオブジェクトを文字列変換して表示しています。

実行例

(3)インスタンス・メソッド

(3-1)リクエストの初期化

新しく作成されたリクエストを初期化したり、既存のリクエストを再初期化したりします。

ただし、すでに有効なリクエスト(すでに open() が呼び出されたもの)に対してこのメソッドを実行すると、abort() を実行したのと同じことになります。

メソッド引数機能戻り値
open(httpメソッド, アクセス先
 [, 非同期
  [, ユーザ名[, パスワード]]]
)
httpメソッド,
説明
GET   規定値データの取得
POSTデータの作成
(リクエストの送信)
HEADヘッダの取得
PUTデータの作成・置換
DELETEデータの削除
リクエストを送信する URL,
非同期的(true)同期的(false),(規定値:true)
認証時のユーザ名,(規定値:null)
認証時のパスワード(規定値:null)
新しく作成されたリクエストを初期化したり、既存のリクエストを再初期化したりする。なし

httpメソッドを指定することにより、行いたい処理をサーバに伝えることができます。この例では、GET、POST、HEAD を指定しています。

なお、この例では、通信は同期的に行っていて send() メソッドはレスポンスを受信するまで戻りません。

記述例
<span id="d1"></span><br>
<!-- 以下略 -->

<script>
function get(url, data) {
  var xhr = new XMLHttpRequest();
  xhr.open('GET', url + "?" + data, false);
  xhr.send(null);
  return xhr.responseText;
}

function post(url, data) {
  var xhr = new XMLHttpRequest();
  xhr.open('POST', url, false);
  xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
  xhr.send(data);
  return xhr.responseText;
}

function head(url) {
  var xhr = new XMLHttpRequest();
  xhr.open('HEAD', url, false);
  xhr.send();
  return xhr.getResponseHeader("date") + "&nbsp;&nbsp;" + xhr.getResponseHeader("content-type");
}

d1.innerHTML = get("cgi/get.cgi", "name=太郎&gender=male");
d2.innerHTML = post("cgi/post.cgi", "name=花子&gender=female");
d3.innerHTML = head("cgi/get.cgi");
</script>
実行例

通信を非同期的に行った場合には、レスポンスを受信する前に send() メソッドを抜けて次の命令を実行してします。

しかし、受信の完了は、イベントによって知ることができます。

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>

<script>
function get(url, data) {
  var xhr = new XMLHttpRequest();
  xhr.onload = function() {d2.innerHTML = xhr.responseText;}   // load イベント後の responseText 内容(受信している)
  xhr.open('GET', url + "?" + data, true);
  xhr.send(null);
  return xhr.responseText;    // send 直後の responseText 内容(まだ受信していない)
}

d1.innerHTML = get("cgi/get.cgi", "name=太郎&gender=male");
</script>

send() 直後の responseText には何も入っていませんが、load イベント後の responseText には受信した内容が設定されています。

実行例

(3-2)送信

サーバへリクエストを送信したり、すでに送信された要求を中止します。

メソッド引数機能戻り値
send([データ])送信データ(POST リクエストの場合)(規定値:null)サーバへリクエストを送信する。なし
abort()なし送信された要求を中止する。なし

Content-Type と送信された内容を表示しています。

ただし、Blob のときは Content-Type はなく、送信された内容は画像データの内容そのままです。この例では、それを base64 形式に変換して表示しています。なお、canvas 要素の画像の Blob への変換は、「11.12 画像出力」を参照してください。

また、Document は内容が多いので ... とし、それ以降を省略して表示しています。

記述例
<span id="d1"></span><br>
<span id="d2"></span><br>
<span id="d3"></span><br>
<img id="d4"><br>
<form form="form">
name: <input name="name" value="小島">
address: <input name="addr" value="茅ヶ崎">
</form>
<span id="d5"></span><br>

<script>
const CGI = "cgi/xmlhttprequest.pl";
var xhr = new XMLHttpRequest();
func1();

function func1() {
  xhr.open("POST", CGI, true);
  xhr.onload = function() { document.getElementById("d1").textContent = xhr.response; func2();};
  xhr.send("普通の文字列") ;
}

function func2() {
  xhr.open("POST", CGI, true);
  xhr.onload = function() { document.getElementById("d2").textContent = xhr.response; func3();};
  var urlSearchParams = new URLSearchParams("name=小島&addr=茅ヶ崎");
  xhr.send(urlSearchParams);
}

function func3() {
  xhr.open("POST", CGI, true);
  xhr.onload = function() { document.getElementById("d3").textContent = xhr.response.substring(0, 200) + " ..."; func4();};
  xhr.send(document);
}

function func4() {
  xhr.open("POST", CGI, true);
  xhr.responseType = 'blob';
  xhr.onload = function() {
    var file = new Blob([xhr.response], {'type':'image/png'});
    var reader = new FileReader();
    reader.onload = function(event) {
      var arr = new Uint8Array(reader.result);      // ③サーバーから送り返された Blob を base64 形式に変換して表示する
      var b = "";
      for(var i = 0, len = arr.length ; i < len ; i++) {
        b += String.fromCharCode(arr[i]);
      }
      document.getElementById("d3").src = "data:image/png;base64," + window.btoa(b);
      func5();
    }
    reader.readAsArrayBuffer(file);
  };
  var image = new Image();
  image.onload = function() {
    var canvas = document.createElement("canvas");
    canvas.height = 80;
    var context = canvas.getContext("2d") ;
    context.drawImage(image, 0, 0, 74, 74);
    if (canvas.toBlob != undefined)                 // ②イメージが読み込まれたら Blob に変換しサーバーに送信する
      canvas.toBlob(function(result) { xhr.send(result); });
    else
      xhr.send(canvas.msToBlob());
  };

  image.src = "figures/bear.png";                   // ①イメージを設定する
}

function func5() {
  xhr.open("POST", CGI, true);
  xhr.onload = function() { document.getElementById("d5").textContent = xhr.response;};
  var formData = new FormData(document.getElementById("form"));
  xhr.send(formData) ;
}
</script>

Document の例でエラーが発生しているかもしれませんが、サーバーの設定の問題かもしれません。

実行例

abort() を実行すると、送信が中断します。

記述例
<span id="d1"></span><br>

<script>
var xhr = new XMLHttpRequest();
xhr.open("POST", "cgi/xmlhttprequest.cgi", true);
xhr.onprogress = function() {xhr.abort();};
xhr.onabort = function() {document.getElementById("d1").textContent = "中断(" + xhr.readyState + ")";};
xhr.onload = function() {document.getElementById("d1").textContent = "完了(" + xhr.readyState + ")";};
xhr.send("普通の文字列") ;
</script>
実行例

(3-3)ヘッダー

メソッド引数機能戻り値
getAllResponseHeaders()なしすべてのヘッダー名を含む文字列を返す。すべてのヘッダー名とその内容
getResponseHeader(ヘッダー名)要求するヘッダーの名前指定したヘッダー名を含む文字列を返す。ヘッダー名とその値
setResponseHeader(ヘッダー名, )設定するヘッダーの名前、設定値指定したヘッダーに値を設定する。なし


記述例
<span id="d1"></span><br>

<script>
var xhr = new XMLHttpRequest();
xhr.addEventListener('load',
  function(){
    document.getElementById("d1").textContent = xhr.getAllResponseHeaders();
    document.getElementById("d2").textContent = xhr.getResponseHeader("Content-Type");
  }, false);
xhr.open("GET", "data/sample.txt", true);
xhr.send();
</script>
実行例

Content-Type は、POST や PUT などの送信において、クライアントからサーバーにどのような種類のデータが実際に送られたかを伝えます。それが正しく設定されていないと適切な処理がされません。

記述例
<span id="d1"></span><br>

<script>
var xhr1 = new XMLHttpRequest();
xhr1.onload = function() { document.getElementById("d1").innerHTML = xhr1.responseText; };
xhr1.open("POST", "cgi/post.cgi", true);
xhr1.setRequestHeader("Content-Type", "text/plain;");             // プレーンテキスト
xhr1.send("name=太郎&gender=male");

var xhr2 = new XMLHttpRequest();
xhr2.onload = function() { document.getElementById("d2").innerHTML = xhr2.responseText; };
xhr2.open("POST", "cgi/post.cgi", true);
xhr2.setRequestHeader("Content-Type", "application/x-www-form-urlencoded;");    // Webサーバーへの POST送信
xhr2.send("name=太郎&gender=male");
</script>
実行例

12.5  非同期読み込み(FileReader)

FileReader オブジェクトを使うと、ユーザーのコンピューター内にあるファイル (もしくはバッファ上の生データ) をウェブアプリケーションから非同期的に読み込むことが出来ます。

読み込むファイルやデータは File ないし Blob オブジェクトとして指定します。

12.5.1  FileReader インスタンス

(1)インスタンス生成

新規の FileReader インスタンスを生成します。

new FileReader()

FileReader インスタンスを生成する。

引数 なし


記述例
<script>
var req = new FileReader();  // ファイルを読み込むためのFileReaderオブジェクトを生成
</script>


(2)インスタンス・プロパティ

プロパティ説明
errorR/ODOMException インスタンスファイルの読込中に生じたエラー
readyStateR/OFileReader の状態を表す数値
EMPTY (0)まだデータは何も読み込まれていない
LOADING (1)データの読み込み中
DONE (2)読込処理がすべて終了した
resultR/O文字列読み込んだファイルの内容

Blobオブジェクトをテキスト文字列として取得してみます。

記述例
<span id="d01"></span><span id="d02"></span> <!-- 略 --> <br>
<!-- 以下略 -->

<script>
function disp(message){
  document.getElementById("d0" + m).textContent = message;
  document.getElementById("d1" + m).textContent = reader.readyState;
  document.getElementById("d2" + m).textContent = reader.result;
  document.getElementById("d3" + m).textContent = reader.error;
  m++;
};

var m = 1;
var reader = new FileReader();     // FileReader オブジェクトを作成
reader.onloadstart = function() {disp("loadstart");};
reader.onprogress = function() {disp("progress");};
reader.onload = function() {disp("load");};
reader.onloadend = function() {disp("loadend");};
reader.onerror = function() {disp("error");};
disp("");
var blob = new Blob(["文字列の取得"], {type:"text/plain"}); // Blob オブジェクトを作成
reader.readAsText(blob); 
</script>
実行例

読込処理を中断させた場合は次のようになります。

実行例



(3)インスタンス・メソッド

メソッド引数機能戻り値
abort()なし読込処理を中断し、readyState を DONE にするなし
readAsArrayBuffer(blob)Blob か
File オブジェクト
Blob や File オブジェクトの内容を読み込み、readyState を DONE にする。
result プロパティにはファイルのデータを表す ArrayBuffer が格納される。
なし
readAsBinaryString(blob)Blob か
File オブジェクト
Blob や File オブジェクトの内容を読み込み、readyState を DONE にする。
result プロパティには生のバイナリデータを文字列で解釈したものが格納される。
なし
readAsDataURL(blob)Blob か
File オブジェクト
Blob や File オブジェクトの内容を読み込み、readyState を DONE にする。
result プロパティには base64 エンコーディングされた URL の文字列が格納される。
なし
readAsText
      (blob[, encoding])
Blob か
File オブジェクト
エンコード(規定値:UTF-8)
Blob や File オブジェクトの内容を読み込み、readyState を DONE にする。
result プロパティにはファイルの内容がテキストとして格納される。
なし
記述例
<span id="d01"></span><span id="d02"></span><br>
<!-- 以下略 -->

<script>
var blob = new Blob(["文字列の取得"], {type:"text/plain"}); // Blob オブジェクトを作成
function readAsArrayBuffer() {
  var reader = new FileReader(); // FileReader オブジェクトを作成
  reader.onload = function() {
    d01.textContent = reader.result.byteLength;
    var data = new Uint8Array(reader.result);
    data.forEach(d => d02.textContent += d.toString(16).toUpperCase() + " ");
  };
  reader.readAsArrayBuffer(blob);
}
function readAsBinaryString() {
  var reader = new FileReader(); // FileReader オブジェクトを作成
  reader.onload = function() {
    d11.textContent = btoa(reader.result);
  };
  reader.readAsBinaryString(blob);
}
function readAsDataURL() {
  var reader = new FileReader(); // FileReader オブジェクトを作成
  reader.onload = function() {
    d21.textContent = reader.result;
  };
  reader.readAsDataURL(blob);
}
function readAsText() {
  var reader = new FileReader(); // FileReader オブジェクトを作成
  reader.onload = function() {
    d31.textContent = reader.result;
  };
  reader.readAsText(blob);
}

readAsArrayBuffer();
readAsBinaryString();
readAsDataURL();
readAsText();
</script>
実行例

abort() によって、読み取り操作を中止させると次のようになります。

記述例
<span id="d01"></span><br>
<!-- 以下略 -->

<script>
function disp(message){
  document.getElementById("d01").textContent = message;
  document.getElementById("d11").textContent = reader.readyState;
  document.getElementById("d21").textContent = reader.result;
  document.getElementById("d31").textContent = reader.error;
};

var reader = new FileReader(); // FileReader オブジェクトを作成
reader.onloadend = function() {disp("loadend");};
reader.onerror = function() {disp("error");};
var blob = new Blob(["文字列の取得"], {type:"text/plain"}); // Blob オブジェクトを作成
reader.readAsText(blob); 
reader.abort();   // 読み取り操作を中止させる  
</script>
実行例

File オブジェクトを使用した例は次のようになります。

記述例
<input id="file" type="file" accept="text/plain">
  エンコード:<select id="encoding">
 <option value="Shift_JIS">シフトJIS</option>
 <option value="UTF-8">UTF-8</option>
</select><br>
<span id="d"></span><br>

<script>
var reader = new FileReader(); // FileReader オブジェクトを作成
reader.onload = function() {
  d.textContent = reader.result;
}
file.onchange = function(e) {
  reader.readAsText(e.target.files[0], encoding.value);
}
</script>
実行例