JavaScript
jQuery
promise
Deferred
Ajax

JavaScriptがちょっとわかる人向けの、Ajaxの話。

Ajaxとは?

Ajaxとは、「Asynchronous JavaScript + XML」の略です。
「Asynchronous = 非同期」という意味なので、Ajaxは非同期JavaScriptXMLということですね。

具体的にはこんな感じです。

english.json
[
  {
    "apple": "りんご",
    "car": "くるま",
    "pencil": "えんぴつ"
  }
]
jQuery
$.ajax({
  url: 'english.json',
  type: 'GET',
  dataType: 'json'
}).done(function(data) {
  console.log(data[0].apple); // りんご
});

XMLとなっていますが、HTMLでもJSONでもPHPでも取得することができます。

english.jsonには、簡単な英和辞典()のデータベースが入っています。
jQueryの$.ajaxでJSONを取得し、data引数に渡しています。

$.ajaxで返ってくるもの。

data引数の中身を見てみると、取得したJSONが格納されています。

jQuery
.done(function(data){
  console.log(data);
});

この状態だけを見ると、$.ajax()は、URLプロパティで指定したファイルだけを取得しているように見えます。
が、実はdata引数の中身は取得したデータの一部分で、実際にはもっと多くの情報を取得しています。

具体的には、$.ajax()の場合、jqXHRオブジェクトを取得しています。

$.ajax().done()では、.done()を第三引数まで指定することができます。

jQuery
.done(function(data, textStatus, jqXHR) {
  console.log('success');
  console.log(data);
  console.log(textStatus);
  console.log(jqXHR);
});

先程のdata引数は、第一引数にあたります。
これは、jqXHRオブジェクトのresponseJsonプロパティ値と同じものです。

第二引数のtextStatusは、通信の結果により、success, notmodified, nocontent, error, timeout, abort, parsererrorのどれかが返ります。

では、jqXHRオブジェクトとは、なんでしょうか?

jqXHRオブジェクトとは?

jQueryの公式ドキュメントを見ると、こう書かれています。

The jqXHR Object
The jQuery XMLHttpRequest (jqXHR) object returned by $.ajax() as of jQuery 1.5 is a superset of the browser's native XMLHttpRequest object. For example, it contains responseText and responseXML properties, as well as a getResponseHeader() method. When the transport mechanism is something other than XMLHttpRequest (for example, a script tag for a JSONP request) the jqXHR object simulates native XHR functionality where possible.

Google先生の力をかりると、

jqXHRオブジェクト
jQuery 1.5以降の$ .ajax()によって返されるjQuery XMLHttpRequest(jqXHR)オブジェクトは、ブラウザのネイティブXMLHttpRequestオブジェクトのスーパーセットです。たとえば、responseTextおよびresponseXMLプロパティ、およびgetResponseHeader()メソッドが含まれています。トランスポート・メカニズムがXMLHttpRequest以外のもの(JSONP要求のスクリプト・タグなど)である場合、jqXHRオブジェクトは可能な場合はネイティブXHR機能をシミュレートします。

ネイティブXMLHttpRequestオブジェクトのスーパーセット
ここがポイントですね。

XMLHttpRequestとは、ネイティブでJavaScriptが用意している、クライアントとサーバー間とをやり取りするためのAPIです。
参照:https://developer.mozilla.org/ja/docs/Web/API/XMLHttpRequest

このAPIを叩くと、XMLHttpRequestオブジェクトを取得することができます。

jqXHRオブジェクトは、XMLHttpRequestオブジェクトをjQueryがより使いやすい形に拡張したものです。

$.ajaxは、$.Deferredの様なPromiseを返す。

通信が成功したjqXHRオブジェクトのpromiseプロパティの中身を見てみると、resolveという値があります。
これは、$.Deferredと似たような仕組みで、ajax通信が終わると、Promiseを返します。

ajax通信が始まる前はpendingという値になっています。
ajax通信が成功すると値がresolveに代わり、失敗するとrejectedに変わります。
resolveになると、done()が実行され、rejectedになるとfail()が実行されます。

jQuery
$.ajax({
  url: 'english.json',
  type: 'GET',
  dataType: 'json'
}).done(function(data, textStatus, jqXHR) {
  console.log(data[0].apple); // りんご
}).fail(function(jqXHR, textStatus, errorThrown){
  console.log('通信失敗');
});

.fail()は、引数が.done()と異なります。

ちなみに、.then()を使うと、成否の両方を繋げて記述することができます。
また、.always()は、成否によって引数の値が変化します。

jQuery
$.ajax({
  url: 'english.json',
  type: 'GET',
  dataType: 'json'
}).then(function(data, textStatus, jqXHR) {
  console.log('成功');
}, function(jqXHR, textStatus, errorThrown) {
  console.log('失敗');
}).always(data|jqXHR, textStatus, jqXHR|errorThrown) {
  // 成功すると.done()の引数になり、失敗すると.fail()の引数になる。
});

より詳しく知りたい方は、公式ドキュメントをご確認くださいm(_ _)m
http://api.jquery.com/jquery.ajax/

applyメソッドを$.whenで使用し、順番を保証。

さて、ここからは少し実践的、かつ、ピンポイントなお話をします。

Ajax通信は非同期な処理なので、次の様な処理で、順番を保証したいときは、少し工夫が必要になります。

01.txt
第一話:プロローグ
02.txt
第二話:君と夏の終わり将来の夢〜(このあと3万行ぐらいリピート)
03.txt
第三話:エピローグ
jQuery
let index = []; //  ['第一話:プロローグ', '第二話:君と夏の終わり〜', '第三話:エピローグ']の順番で格納したい。

const textFiles = [
  '01.txt',
  '02.txt',
  '03.txt'
];

for (let i = 0; i < textFiles.length; i++) {
  $.ajax({
    url: textFiles[i],
    type: 'GET',
    dataType: 'json'
  }).done(function(data) {
    index.push(data);
  });
}

console.log(index); // ['第一話:プロローグ', '第二話:君と夏の終わり〜', '第三話:エピローグ']とは限らない。

このような書き方ですと、Promiseが返ったタイミングで.done()が実行されてしまうので、取得に時間がかかると順番が変わってしまいます。

そこで、$.when.apply()を使い、順番を保証してあげます。

jQuery
let jqXHRs = [];
let index = [];

const textFiles = [
  '01.txt',
  '02.txt',
  '03.txt'
];

for (let i = 0; i < textFiles.length; i++) {
  jqXHRs.push($.ajax({
    url: textFiles[i],
    type: 'GET',
    dataType: 'text'
  }));
}

$.when.apply($, jqXHRs).done(function(){
  for (let i = 0; i < arguments.length; i++) {
    index.push(arguments[i][0]);
  }
  console.log(index); // ['第一話:プロローグ', '第二話:君と夏の終わり〜', '第三話:エピローグ']
});

$.ajax()で取得したjqXHRオブジェクトは、.push()された順番にjqXHRs配列に格納されます。
ポイントは、.apply()を利用し、$.when()に可変長引数を渡しているところです。

.apply()の第一引数は、$.when()関数を呼び出す際に渡すthisの値です。
今回は、jqXHRオブジェクトの親オブジェクトを呼び出しているので、$を指定しています。

そして、第二引数でjqXHRs配列を指定することにより、すべてのjqXHRオブジェクトがPromiseを返したタイミングで、$.when().done()を実行しています。

実引数のargumentsは、.done()の仮引数であるdata, textStatus, jqXHRの3つの引数を、配列のような形で所有しているので、for文で回してあげることにより、順番を保証したままdataを取得することができます。

もし、意味がわからなかったら。。。

ここまでの話の意味がわからなかった方は、オライリーの「開眼!JavaScript」を一読することをおすすめします。

とても勉強になります。

ECMAScript2015でXMLHttpRequest

おまけで、ECMAScript2015XMLHttpRequestオブジェクトを取得する方法を書いておきます。

JavaScript
const _url = 'english.json'; // クロスドメインに注意

const foo = (url) => {
  return new Promise((resolve, reject) => {
    const xhr = new XMLHttpRequest();
    xhr.open('GET', url);
    xhr.onload = () => resolve(xhr.response);
    xhr.onerror = () => reject(xhr.statusText);
    xhr.send();
  });
};

foo(_url).then((data) => {
  const myData = JSON.parse(data); // IE11ではjsonがパースされないため、文字列として受け取りパースする。
  console.log(myData);
}).catch((error) => {
  console.log(error);
});

ご参考までに。