Ajaxで非同期通信を大量に送信する際、少しずつ送るようにしてみた

担当したシステムの管理画面に
ちょっと特殊な検索をする画面があり苦労したのでちょっと書いときます。

まず、よくある検索画面ですが
検索画面の検索条件に入力/選択を行った後、検索ボタンを押す。
Server側では
条件を複数選択した場合はAND条件（場合によってはOR条件）として処理を行い、
Clientに結果を返す。

Client側は検索結果一覧として特定の列情報を表示する。
という流れになりますが、

今回のは違いました。
条件は選択/入力していくのですが、
これら条件が組み合わせ？的な感じになっていくというもので・・・

書いててもよくわからないですねｗ
例を書くとこんな感じです。

種別A ： A1 / A2 / A3 / A4 / A5
種別B ： B1 / B2 / B3

という種別があったとして

条件1として
種別Aが A1 / A2 / A3 を選択

条件2として
種別Bが B1 / B2 を選択

とした場合、期待される結果表は

| 条件1 |   値 | 件数 | 条件2 | 値     | 件数|
|:-----:|:----:|----:|:-----:|:-----:|----:|
| 種別A |  A1  |  50  | 種別B |   B1  |  10 |
|       |      |      |      |   B2  |  15 |
|       |      |      |      |上記以外|  25 |
|       |  A2  |  30  | 種別B |   B1  |  0  |
|       |      |      |      |   B2  |  20 |
|       |      |      |      |上記以外|  10 |
|       |  A3  |  40  | 種別B |   B1  |  30  |
|       |      |      |      |   B2  |   0  |
|       |      |      |      |上記以外|  10  |

条件2は、条件1の中での件数、
つまり表の右上のB1の10件は

>種別AがA1(=50件)の中に種別BがB1の件数が10件ある
>という見方になります。

実際はtableタグなので空セルがでないようにcolspan/rowspanでセル結合してますが、
見た目はまぁこんな感じです。

また、
・表の件数をClickするとその一覧を表示する画面に遷移する
・この表をエクセルで出力する
・条件の上限は設定しない
という要件でした。

この画面/機能は見た目ほどたいした開発ではなかったので問題なかったんですが、
リリース後、条件を4個以上入れると動かなくなるという報告が。

原因を調べたところ
検索ボタン押下後にクライアントとサーバ間で通信が4~5回発生するんですが
そのうちの
テーブルタグ生成後、件数列のTDタグに対して

```
  $('td.count').each(function(index, obj){
    サーバに該当セルの条件で検索するリクエストを投げる
  });
```

こんな感じでjsを組んでまして・・・
サーバ側でDBコネクションが不足してタイムアウトエラーになってました…

**はい。ここまで前置きです。長くてすみません。**

##対処

まずは実際にサーバへ投げる部分のfunctionがコレです。

```
  function call_server(url, data, func, func_none, func_err) {
    // カーソルをwaitに設定
    document.body.style.cursor='wait';

    var req = null;
    if (window.XMLHttpRequest) { // Non-IE browsers or IE7
      req = new XMLHttpRequest();
    } else {                     // IE6
      req = new ActiveXObject("Microsoft.XMLHTTP");
    }

    if (req) {
      // 結果 の戻る処理関数を関連
      req.onreadystatechange = function(){
        if (req.readyState == 4) {   // Complete
          if (req.status == 200) { // OK response
            var result = req.responseText;
            if(result != null){
              //戻りテキストあり
              func(result);
            }else{
              //戻りテキストなし
              func_none();
            }
          }else{
            func_err(req.status);
          }
        }
        document.body.style.cursor='default';
      };
      try {
        req.open("POST", url, true);
        req.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
        req.setRequestHeader("If-Modified-Since", "Thu, 01 Jun 1970 00:00:00 GMT");
        req.send(data);
      } catch (e) {
        func_err(e);
      }
    }
  }
```

これを先ほど書いた

```
  $('td.count').each(function(index, obj){
    サーバに該当セルの条件で検索するリクエストを投げる
  });
```

ここからガンガンコールしてました・・・
これをfunctionを追加して中継するように修正。
※ここもsetTimeout経由で呼び出す

追加した変数/定数およびfunctionがこちら。

```
//定義
var MAX_SEARCH = 5;
var WAIT_SEARCH = 800;
//実行中検索数
var cnt_searching = 0;

function call_server_slowly(url, data, func, func_none, func_err) {
  if (cnt_searching > MAX_SEARCH) {
    setTimeout(
      function(){
        call_server_slowly(
          url,
          data,
          func,
          func_none,
          func_err
        );
      },
      WAIT_SEARCH
    );
  }else{
    cnt_searching++;
    //
    call_server(
      url,
      data,
      function(v){
        cnt_searching--;
        func(v);
      },
      func_none,
      func_err
    );
  }
}
```

MAX_SEARCH と WAIT_SEARCH
はそれぞれサーバ環境と相談して定義してください。。
検索ボタン押下直後に
cnt_searchingを初期化するのを忘れずに。

ちなみにテスト環境では発見できなかったのは
件数が少なすぎて速攻で処理が終わってたためでした・・・

よくあるといえばよくあるミス・・・
皆様も今後気をつけましょう