jQueryのDeferredを用いたAjaxでRetryを実装

### 要件

* AJAXで所定のURLにアクセスする
* アクセスに成功した時、JSONデータを返す
* アクセスに失敗した時、5回、再試行を繰り返す
* 再試行にも失敗した場合、タイムアウトエラーを返す

### 実装

動作するためにjQueryが必要です。
筆者環境は「jquery-1.11.3」です。
なるべく汎用的に使いまわせるよう意識して書きました。

```javascript
(function($){
'use strict';

var CONST = {
  'JSON' : {
    'PATH' : "/api/v1/items/data.json", // <- このあたりのURLをお使いの環境に合わせて変更してください
  },
};

Object.freeze(CONST);

/**
 * connect Ajax
 *
 * @param string targetPath
 * @param string method     ["GET", "POST"]
 * @param string dataFormat ["json", "jsonp", "xml", "html", "script", "text"]
 */
function connectAjax(targetPath, method, dataFormat)
{
  var maxRetries = 5; // 最大リトライ回数

  var retries = 0; // 現在のリトライ回数
  var retryTime = 1000; // リトライの間隔 ミリ秒

  var defer = $.Deferred();

  $.ajax({
    type : method,
    url : targetPath,
    dataType : dataFormat,
    cache : false,
    timeout : retryTime,
    success : defer.resolve,
    error : function(jqXHR, textStatus, errorThrown){
              if(retries < maxRetries){
                var _this = this;
                retryTime = retryTime * 2; // リトライを繰り返すごとにリトライ間隔を延長します
                _this.timeout = retryTime;
                console.warn("Connection Faild. Retry After " + retryTime + " (ms)");

                retries++;
                setTimeout(function(){ $.ajax(_this) }, retryTime); // retry begin
              }
              else {
                console.warn("Connection Timeout.");
                console.warn(jqXHR.status + " " + jqXHR.statusText);
                console.warn(showException(jqXHR.status, textStatus));

                defer.reject(); // retry end
              }
            },
  });

  return defer.promise();
}

function responceMessages()
{
   return {
     '400' : "Server understood the request, but request content was invalid.",
     '401' : "Unauthorized access.",
     '403' : "Forbidden resource can't be accessed.",
     '500' : "Internal server error.",
     '503' : "Service unavailable."
   };
}

function exceptionMessages()
{
   return {
     'parsererror' : "Parsing JSON Request failed.",
     'timeout'     : "Request Time out.",
     'abort'       : "Request was aborted by the server",
   };
}

function responceMessage(responceCode)
{
  var resMsgs = responceMessages();

  var msg = responceCode + " : ";
  var resMsg = resMsgs[responceCode];

  resMsg = isset(resMsg) ? resMsg : "An Unknown Error Occurred.";

  return msg + resMsg;
}

function exceptionMessage(textStatus)
{
  var exMsgs = exceptionMessages();

  var msg = textStatus + " : ";
  var exMsg = exMsgs[textStatus];

  exMsg = isset(exMsg) ? exMsg : "An Unknown Error Occurred.";

  return msg + exMsg;
}

function showException(responceCode, textStatus)
{
  var resMsg = responceMessage(responceCode);
  var exMsg = exceptionMessage(textStatus);

  return resMsg + "\n" + exMsg;
}

function loadJson(jsonPath)
{
  return connectAjax(jsonPath, "GET", "json");
}

function getItemList()
{
  return loadJson(CONST.JSON.PATH);
}

})(jQuery);
```

### 使い方

```javascript
$(function(){
  var itemList = getItemList(); // ajax connect

  itemList.done(function(data){
    console.log(data);
    // ここにAJAX成功時にやりたいことを書きます
  })
  .fail(function(){
    console.log('Failed');
    // リトライにすべて失敗した後に、ここに書いた内容が走り出します
  });
});
```

### 参考にしたURL

https://qiita.com/toxxhixx/items/7b0e77654a19a670722f
https://qiita.com/mounntainn/items/37407e1ced0ce8c23101
https://iww.hateblo.jp/entry/20160804/setTimeout
https://stackoverflow.com/questions/10024469/whats-the-best-way-to-retry-an-ajax-request-on-failure-using-jquery
https://stackoverflow.com/questions/377644/jquery-ajax-error-handling-show-custom-exception-messages