ど忘れSEの備忘録

日々集めた何かしらを残すための何か。 共有とか疑問解決とかできたらいいな。 iPhoneとかiOSとかその他スクリプトとか色々。

【解決】続・jQueryとAjax使ってforでループしながらigComboの中身を初期化したいよっていうお話

昨日の記事の続き。
forgeserem.hatenablog.com

ただ、どうせ全通信の完了を待つなら「async : false」を取ればいいやと思いコメントアウトすると初期化ができなくなった。

と書いたけど、非同期の場合
forループ回しきる(ただし.thenの処理は行わない)
 ↓
.when到達
 ↓
.ajax内の.then実行
となるようで、.whenに到達した時点でループ変数iがMAX値に達しており、.then実行時に配列の参照範囲を超えているためエラーとなっていた模様。
同期の場合は先にforループ内で.thenの処理を行うため正しく配列の参照ができていたっていうのが今回のオチ。

なので、.when到達前にiを初期化してやって、.thenの中でiをカウントアップすればよかったみたい。

正解としてはこんな感じ。
(追記 正解ではなかった!さらに追記で補足。)

$("#hoge1").igCombo({
  textKey: "hogeKey1",
  valueKey: "hogeVal"
});

$("#hoge2").igCombo({
  textKey: "hogeKey2",
  valueKey: "hogeVal"
});


(中略)

// ajaxによるコンボBOX設定
// urlはコントローラのRequestMapping
// 初期値指定がある場合にも対応したかった
var requestHdr = [
  { url: $("#contextPath").val() +"/hoge/getHoge1", id: "#hoge1", defaultVal: ""},
  { url: $("#contextPath").val() +"/hoge/getHoge1", id: "#hoge2", defaultVal: "ALL"}
];
var jqXHRListHdr = [];  // 実行するAjaxを格納する配列

for (var i = 0; i < requestHdr.length; i++) {
  jqXHRListHdr.push(
      $.ajax({
        type          : "POST",
        data          : JSON.stringify(jsonData),
        url           : requestHdr[i].url,           // 配列で指定したurlを順次受け取る
        cache         : false,
        contentType   : 'application/json',
        //async         : false,
        scriptCharset : 'utf-8'
      })
      .then(
        // 通信成功時のコールバック
        function(data) {
          $(requestHdr[i].id).igCombo("option", "dataSource", data);
          // 初期値が指定されている場合はその値を設定
          if (requestHdr[i].defaultVal != "") {
            $(requestHdr[i].id).igCombo("value", requestHdr[i].defaultVal);
          }
          //【追加箇所】非同期の場合forループ内で実行されないため独自にカウントアップ
          i++;
        },
        // 通信失敗時のコールバック
        function(xhr, textStatus, errorThrown) {
          var errorInfo = $.parseJSON(xhr.responseText);
          location.href($("#contextPath").val() +"/sysError/"+ errorInfo.errNo +"/"+ errorInfo.errDate +"/"+ errorInfo.errContents);
          //【追加箇所】非同期の場合forループ内で実行されないため独自にカウントアップ
          i++;
        }
      )
  );
}

// ajaxによる処理を実行
//【追加箇所】コールバック用に変数初期化
i = 0;
// $.when関数にてまとめてAjax通信を実施
$.when(jqXHRListHdr).done(function () {
    console.log("通信成功");
    後続処理();
}).fail(function (ex) {
    console.log("Ajax通信に失敗しました");
});

こんな感じで3行追加してあげれば非同期での動作が可能になった。
スッキリはしたけど、こんな使い方あってるんだろうか…。

追記
そもそもwebパーツの画面描画が遅延した場合、この処理だと想定しないコンボBOXに初期値を投入してしまう場合があるため却下。
hoge2にhoge1の初期値が投入される場合がある模様。
同期版で対応するか、画面描画まで含めて.thenの処理に加えないとダメかも…。
今回は前回の記事版で対応することにしよう。

さらに追記
あーそうか、.then内でi定義してないから必然的に外側のiを見に行っちゃうってだけか…。
forEachにするだけで初期化とかインクリメントとか必要なくなる。
ついでに.whenもこれだと.thenが後になってしまうため、.when.applyにする必要あり。
最終的には↓でFIXかな。

$("#hoge1").igCombo({
  textKey: "hogeKey1",
  valueKey: "hogeVal"
});

$("#hoge2").igCombo({
  textKey: "hogeKey2",
  valueKey: "hogeVal"
});


(中略)

// ajaxによるコンボBOX設定
// urlはコントローラのRequestMapping
// 初期値指定がある場合にも対応したかった
var requestHdr = [
  { url: $("#contextPath").val() +"/hoge/getHoge1", id: "#hoge1", defaultVal: ""},
  { url: $("#contextPath").val() +"/hoge/getHoge1", id: "#hoge2", defaultVal: "ALL"}
];

var jqXHRListHdr = [];  // 実行するAjaxを格納する配列

requestHdr.forEach(function(requestData, index) {
  jqXHRListHdr.push(
      $.ajax({
        type          : "POST",
        data          : JSON.stringify(jsonData),
        url           : requestHdr.url,           // 配列で指定したurlを順次受け取る
        cache         : false,
        contentType   : 'application/json',
        //async         : false,
        scriptCharset : 'utf-8'
      })
      .then(
        // 通信成功時のコールバック
        function(data) {
          console.log(index + " : " + requestData.id + " : データ取得開始");
          $(requestHdr.id).igCombo("option", "dataSource", data);
          // 初期値が指定されている場合はその値を設定
          if (requestHdr.defaultVal != "") {
            $(requestHdr.id).igCombo("value", requestHdr.defaultVal);
          }
        },
        // 通信失敗時のコールバック
        function(xhr, textStatus, errorThrown) {
          var errorInfo = $.parseJSON(xhr.responseText);
          location.href($("#contextPath").val() +"/sysError/"+ errorInfo.errNo +"/"+ errorInfo.errDate +"/"+ errorInfo.errContents);
        }
      )
  );
});

// ajaxによる処理を実行
// $.when関数にてまとめてAjax通信を実施
console.log("Ajax定義完了");
$.when.apply($, jqXHRList).done(function () {
    console.log("後続処理呼び出し");
    後続処理();
}).fail(function (ex) {
    console.log("Ajax通信に失敗しました");
});

この実行結果が

hogehoge.js:212 Ajax定義完了
hogehoge.js:199 0 : #hoge1 : データ取得開始
hogehoge.js:199 1 : #hoge2 : データ取得開始
hogehoge.js:214 後続処理呼び出し

こんなログになるので後続処理前に.thenが呼ばれてることが確認できた。
たまに.thenで吐いてるログのindexが前後することはあるけど、配列用indexの整合性が保たれているため画面描画も問題なし。
めでたしめでたし。