【Promise】再施行出来るPromise (Retmise)

# モジュール定義

以前に[「非同期な自動再試行をネイティブのPromiseを用いて実装」](http://qiita.com/mpyw/items/d3457a79c6bcbc71d898)で実装したときは用途が限られてしまったけど、こっちなら汎用的に使える。


```js:retmise.js
(function (root) {
    
    'use strict';

    function Retmise(options) {
        if (this == null || this.constructor !== Retmise) {
            return new Retmise(options);
        }
        options         = options || {};
        options.retries = options.retries != null ? options.retries : 5;
        options.delay   = options.delay   != null ? options.delay   : 500;
        options.timeout = options.timeout != null ? options.timeout : 0;
        options.onRetry = typeof options.onRetry === 'function' ? options.onRetry : doNothing;
        this.options    = options;
    }

    Retmise.prototype.do = function (callback) {
        var _this = this;
        var count = 0;
        var rejected = false;
        return new Promise(function (resolve, reject) {
            (function self() {
                new Promise(callback).then(function (result) {
                    resolve(result);
                }, function (reason) {
                    if (count++ < _this.options.retries || _this.options.retries < 0) {
                        // 試行回数内なら再試行を予約
                        setTimeout(function () {
                            if (!rejected) {
                                // まだRejectされていなければ再試行
                                _this.options.onRetry(count);
                                self();
                            }
                        }, _this.options.delay);
                    } else {
                        reject(reason);
                    }
                });
            })();
            if (_this.options.timeout > 0) {
                setTimeout(function () {
                    // これ以上再試行しないようにフラグをセット
                    rejected = true;
                    reject('timeout');
                }, _this.options.timeout);
            }
        });
    };

    function doNothing() {}
    
    // CommonJSのrequire, Browserの通常読み込みに対応
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = Retmise;
    } else {
        root.Retmise = Retmise;
    }

})((this || 0).self || global); // Browser, Node.js, WebWorker に対応
```

## `new Retmise(options)`

- オブジェクト`options`は以下の値を取ります。キーは全て省略可能であり、また引数自体も省略可能です。このオブジェクトはコピーされて`Retmise`オブジェクト内部に保持されるので、後からの変更は適用されません。

| キー | デフォルト値 | 値の説明 |
|:---:|:----:|:----:|
|`retries`|5|リトライ回数。**0未満で無限試行。**
|`delay`|500|遅延させるミリ秒。0以下で即時再試行。|
|`timeout`|0|タイムアウトまでのミリ秒。0以下で無制限。|
|`onRetry`|空の関数|第1引数を**初回を含む**それまでの試行回数とする関数。<br />リトライごとに実行される。|

- 引数自体を省略可能です。
- ガード節があるので`new`を省略可能です。

## `Retmise.prototype.do(callback)`

通常の`Promise`のコンストラクタに相当する部分です。通常の`Promise`を返します。自動的に再試行が行われる以外は全く同じです。

# 使用例

```html:3000ミリ秒後にコンソールに出力される例
<!DOCTYPE html>
<title>Test</title>
<script src="retmise.js"></script>
<script>
    Retmise({retries: 5, delay: 1000}).do(function (resolve, reject) {
        var element = document.querySelector('div');
        element ? resolve(element) : reject('failed');
    }).then(function (element) {
        console.log(element); 
    }, function (reason) {
        console.error(reason);
    });
    setTimeout(function () {
        document.body.appendChild(document.createElement('div'));
    }, 3000);
</script>
```