文章出處

  JavaScript的Deferred是比較高大上的東西,  主要的應用還是主ajax的應用,  因為JS和nodeJS這幾年的普及,  前端的代碼越來越多,  各種回調套回調再套回調實在太讓人崩潰, 所以就從后端拖了一個延遲對象這貨, 用來解決回調地獄這個問題 。

   我們使用ajax的時候多數都是為ajax添加回調 ,然后異步向服務器發送請求, 比如如下原生的XHR代碼:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>
        填坑必備
        </title>
    </head>
    <body>
        <div id="results">
            data
        </div>
        <script>
            var results = document.getElementById('results');
            var r = new XMLHttpRequest();
            r.open("GET", "http://www.filltext.com?rows=10&f={firstName}", true);
            r.onreadystatechange = function () {
                if (r.readyState != 4 || r.status != 200) return;
                var data = JSON.parse(r.responseText);
                for (i=0;i<data.length;i++){
                    results.innerHTML += '<li>'+data[i].f+'</li>'
                };
            };
            r.send();
        </script>
    </body>
</html>

 

   因為jQ1.5以后版本的ajax的實例對象繼承了延遲對象, 我們可以使用ajax實例的then或者done以及fail等等方法 ,所以上面的代碼可以改寫為:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
        <title>
        填坑必備
        </title>
    </head>
  //bootcdn這個cdn滿好用的, 上面好多庫哇, 推薦一下; <script src="http://cdn.bootcss.com/jquery/2.1.3/jquery.min.js"></script> <body> <div id="results"> data </div> <script>
                                      //then : $.get, $.post, $.ajax返回的對象是延遲對象(deferred);
$.get("http://www.filltext.com?rows=10&f={firstName}").then(function(data){
          console.log(data);
for (i=0;i<data.length;i++){ $("#results").html(function(){ return $.map(data,function( obj ) { return obj.f }).join(" || ") }) }; }); </script> </body> </html>

 

  下劃線的這個是網上抄的,不想看可以略過:異步模式在web編程中變得越來越重要,對于web主流語言Javscript來說,這種模式實現起來不是很利索,為此,許多Javascript庫(比如 jQuery和Dojo)添加了一種稱為promise的抽象(有時也稱之為deferred)。通過這些庫,開發人員能夠在實際編程中使用 promise模式。

  先說下延遲對象的三種狀態, 當一個延遲對象被初始化那么該對象默認為peding狀態

    1:pending等待狀態

     2:fulfilled執行完成狀態

     3:rejected拒絕狀態;

  延遲對象的兩個主要方法:

    1:add 這個方法是添加成功回調

    2:fail 這個方法是添加拒絕回調

  延遲對象比較高級的方法:

    1:then方法; 該方法返回一個包裹了舊延遲對象新延遲對象

    2:promise方法;這個對象返回的是閹割后的Defered(延遲對象),主要作用是保護原來延遲對象;

    3:when這個方法不是在延遲對象的實例上, (根據庫的不同, when這個方法在不同的地方,比如jQ的when方法是$.when,別的庫( •̀ ω •́ ) 也說不準, 反正when的源代碼是在Deferred附近), 但是和延遲對象息息相關, 因為這個方法返回的也是一個延遲對象, 顧名思義when的作用就是:when你傳的幾個延遲對象全部resolve以后, when的返回實例會resolve....懂得自然懂, 不急;

   下面這張圖是jQ這個Deferred實例對象方法, 提供參考:

  以下的JS代碼是《司徒框架設計》里面介紹的延遲對象mochikit, 可以自己在控制臺跟蹤一下就知道執行的流程:
mochikit延遲對象源代碼如下, 這個延遲對象很好理解;GitHub的地址是: https://github.com/mochi/mochikit/

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>無標題文檔</title>
</head>
<body>
<script>
/** @id MochiKit.Async.Deferred */
var MochiKitDeferred = function (/* optional */ canceller) {
    this.chain = [];
    this.id = this._nextId();
    this.fired = -1;
    this.paused = 0;
    this.results = [null, null];
    this.canceller = canceller;
    this.silentlyCancelled = false;
    this.chained = false;
    this.finalized = false;
    this.GenericError = function(msg){
        return new Error("GenericError"+msg);
    };
    this.CancelledError = function(msg) {
        return new Error("CancelledError"+msg);
    };
};

MochiKitDeferred.prototype = {
    /** @id MochiKit.Async.Deferred.prototype.repr */
    repr: function () {
        return 'Deferred(' + this.id + ', ' + this.state() + ')';
    },

    toString: "",

    _nextId: function() {
        //return setTimeout("",0),中間要有一個空格, 要么不行;
        return setTimeout(" ",0);
    },

    /** @id MochiKit.Async.Deferred.prototype.state */
    state: function () {
        if (this.fired == -1) {
            return 'unfired';
        } else if (this.fired === 0) {
            return 'success';
        } else {
            return 'error';
        }
    },
    /** @id MochiKit.Async.Deferred.prototype.cancel */
    cancel: function (e) {
        var self = this;
        if (this.fired == -1) {
            if (this.canceller) {
                this.canceller(this);
            } else {
                this.silentlyCancelled = true;
            }
            if (this.fired == -1) {
                if (typeof(e) === 'string') {
                    e = new self.GenericError(e);
                } else if (!(e instanceof Error)) {
                    e = new self.CancelledError(this);
                }
                this.errback(e);
            }
        } else if ((this.fired === 0) && (this.results[0] instanceof self.Deferred)) {
            this.results[0].cancel(e);
        }
    },

    _resback: function (res) {
        /***

         The primitive that means either callback or errback

         ***/
        this.fired = ((res instanceof Error) ? 1 : 0);
        this.results[this.fired] = res;
        if (this.paused === 0) {
            this._fire();
        }
    },

    _check: function () {
        if (this.fired != -1) {
            if (!this.silentlyCancelled) {
                throw new MochiKit.Async.AlreadyCalledError(this);
            }
            this.silentlyCancelled = false;
            return;
        }
    },

    /** @id MochiKit.Async.Deferred.prototype.callback */
    callback: function (res) {
        this._check();
        if (res instanceof MochiKit.Async.Deferred) {
            throw new Error("Deferred instances can only be chained if they are the result of a callback");
        }
        this._resback(res);
    },

    /** @id MochiKit.Async.Deferred.prototype.errback */
    errback: function (res) {
        this._check();
        var self = MochiKit.Async;
        if (res instanceof self.Deferred) {
            throw new Error("Deferred instances can only be chained if they are the result of a callback");
        }
        if (!(res instanceof Error)) {
            res = new self.GenericError(res);
        }
        this._resback(res);
    },

    /** @id MochiKit.Async.Deferred.prototype.addBoth */
    addBoth: function (fn) {
        return this.addCallbacks(fn, fn);
    },

    /** @id MochiKit.Async.Deferred.prototype.addCallback */
    addCallback: function (fn) {
        if (arguments.length > 1) {
            fn = MochiKit.Base.partial.apply(null, arguments);
        }
        return this.addCallbacks(fn, null);
    },

    /** @id MochiKit.Async.Deferred.prototype.addErrback */
    addErrback: function (fn) {
        if (arguments.length > 1) {
            fn = MochiKit.Base.partial.apply(null, arguments);
        }
        return this.addCallbacks(null, fn);
    },

    /** @id MochiKit.Async.Deferred.prototype.addCallbacks */
    addCallbacks: function (cb, eb) {
        if (this.chained) {
            throw new Error("Chained Deferreds can not be re-used");
        }
        if (this.finalized) {
            throw new Error("Finalized Deferreds can not be re-used");
        }
        this.chain.push([cb, eb]);

        //已經觸發了, 讓他emitDirect;
        if (this.fired >= 0) {
            this._fire();
        }
        return this;
    },

    _fire: function () {
        /***

         Used internally to exhaust the callback sequence when a result
         is available.

         ***/
        var chain = this.chain;
        var fired = this.fired;
        var res = this.results[fired];
        var self = this;
        var cb = null;
        while (chain.length > 0 && this.paused === 0) {
            // Array
            var pair = chain.shift();
            var f = pair[fired];
            if (f === null) {
                continue;
            };
            try {
                res = f(res);
                fired = ((res instanceof Error) ? 1 : 0);
                if (res instanceof MochiKitDeferred) {
                    //new如果返回的是延遲對象, 那么this的.paused就被卡住了;
                    cb = function (res) {
                        self.paused--;
                        self._resback(res);
                    };
                    /*
                    */
                    this.paused++;
                };
            } catch (err) {
                fired = 1;
                if (!(err instanceof Error)) {
                    err = new MochiKitDeferred.GenericError(err);
                }
                res = err;
            };
        };
        this.fired = fired;
        this.results[fired] = res;
        if (this.chain.length == 0 && this.paused === 0 && this._finalizer) {
            this.finalized = true;
            this._finalizer(res);
        }
        if (cb && this.paused) {
            // this is for "tail recursion" in case the dependent deferred
            // is already fired
            res.addBoth(cb);
            res.chained = true;
        }
    }
};

//這個延遲對象最常用方式是這樣:
var df = new MochiKitDeferred();
df.addBoth(function(){ 
    console.log(1);
}).addBoth(function(){ 
    console.log(2) 
})
df._resback(); // 控制他打出 ==>1 \n\r   2 \n\r;

//這個延遲對象最常用方式是這樣; //當然, 你可以把上面一個函數的返回值作為下一個函數的參數, 如下: var df = new MochiKitDeferred(); df.addBoth(function(){ return 0 }).addBoth(function(arg){ console.log(arg); return 1; }).addBoth(function(arg){ console.log(arg); return 2; }).addBoth(function(arg){ console.log(arg); }) df._resback(); // 控制他打出 ==>1 \n\r 2 \n\r; </script> </body> </html>

 


  博客園上博友教你寫一個比較簡單的延遲對象, 地址是: http://www.cnblogs.com/ygm125/p/3735677.html

  是代碼量比較少的, 也好理解, 實在不懂就開控制器一步一步跟蹤, 多看幾遍, 不懂都說不過去:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>無標題文檔</title>
</head>
<body>
<script>

(function(window,undefined){

    var PENDING = undefined, FULFILLED = 1, REJECTED = 2;
    
    var isFunction = function(obj){
        return 'function' === typeof obj;
    }
    var isArray = function(obj) {
        return Object.prototype.toString.call(obj) === "[object Array]";
    }
    var isThenable = function(obj){
        return obj && typeof obj['then'] == 'function';
    }
    
    var transition = function(status,value){
        var promise = this;
        if(promise._status !== PENDING) return;
        // 所以的執行都是異步調用,保證then是先執行的
        setTimeout(function(){
            promise._status = status;
            publish.call(promise,value);
        });
    }
    var publish = function(val){
        var promise = this,
            fn,
            st = promise._status === FULFILLED,
            queue = promise[st ? '_resolves' : '_rejects'];
        
        while(fn = queue.shift()) {
            val = fn.call(promise, val) || val;
        }
        promise[st ? '_value' : '_reason'] = val;
        promise['_resolves'] = promise['_rejects'] = undefined;
    }
    
    var Promise = function(resolver){
        if (!isFunction(resolver))
            throw new TypeError('You must pass a resolver function as the first argument to the promise constructor');
        if(!(this instanceof Promise)) return new Promise(resolver);
    
        var promise = this;
        promise._value;
        promise._reason;
        promise._status = PENDING;
        promise._resolves = [];
        promise._rejects = [];
        
        var resolve = function(value){
            transition.apply(promise,[FULFILLED].concat([value]));
        }
        var reject = function(reason){
            transition.apply(promise,[REJECTED].concat([reason]));
        }
        
        resolver(resolve,reject);
    }
    
    Promise.prototype.then = function(onFulfilled,onRejected){
        var promise = this;
        // 每次返回一個promise,保證是可thenable的
        return Promise(function(resolve,reject){
            function callback(value){
              var ret = isFunction(onFulfilled) && onFulfilled(value) || value;
              if(isThenable(ret)){
                ret.then(function(value){
                   resolve(value);
                },function(reason){
                   reject(reason);
                });
              }else{
                resolve(ret);
              }
            }
            function errback(reason){
                reason = isFunction(onRejected) && onRejected(reason) || reason;
                reject(reason);
            }
            if(promise._status === PENDING){
                promise._resolves.push(callback);
                promise._rejects.push(errback);
            }else if(promise._status === FULFILLED){ // 狀態改變后的then操作,立刻執行
                callback(promise._value);
            }else if(promise._status === REJECTED){
                errback(promise._reason);
            }
        });
    }
    
    Promise.prototype.catch = function(onRejected){
        return this.then(undefined, onRejected)
    }
    
    Promise.prototype.delay = function(ms){
        return this.then(function(val){
            return Promise.delay(ms,val);
        })
    }
    
    Promise.delay = function(ms,val){
        return Promise(function(resolve,reject){
            setTimeout(function(){
                resolve(val);
            },ms);
        })
    }
    
    Promise.resolve = function(arg){
        return Promise(function(resolve,reject){
            resolve(arg)
        })
    }
    
    Promise.reject = function(arg){
        return Promise(function(resolve,reject){
            reject(arg)
        })
    }
    
    Promise.all = function(promises){
        if (!isArray(promises)) {
            throw new TypeError('You must pass an array to all.');
        }
        return Promise(function(resolve,reject){
            var i = 0,
                result = [],
                len = promises.length;
    
            function resolver(index) {
              return function(value) {
                resolveAll(index, value);
              };
            }
    
            function rejecter(reason){
                reject(reason);
            }
    
            function resolveAll(index,value){
                result[index] = value;
                if(index == len - 1){
                    resolve(result);
                }
            }
    
            for (; i < len; i++) {
                promises[i].then(resolver(i),rejecter);
            }
        });
    }
    
    Promise.race = function(promises){
        if (!isArray(promises)) {
            throw new TypeError('You must pass an array to race.');
        }
        return Promise(function(resolve,reject){
            var i = 0,
                len = promises.length;
    
            function resolver(value) {
                resolve(value);
            }
    
            function rejecter(reason){
                reject(reason);
            }
    
            for (; i < len; i++) {
                promises[i].then(resolver,rejecter);
            }
        });
    }
    
    window.Promise = Promise;

})(window);

//常見的使用方式如下:
var getData100 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve('100ms');
        },100);
    });
}

var getData200 = function(){
    return Promise(function(resolve,reject){
        setTimeout(function(){
            resolve('200ms');
        },200);
    });
}

getData100().then(function(data){
    console.log(data); // 100ms
    return getData200();
}).then(function(data){
    console.log(data); // 200ms
    return data + data;
}).then(function(data){
    console.log(data) // 200ms200ms
});
</script>
</body>
</html>
View Code

 

  

  公司這邊的老段寫的Deferred, 是從TypeScript編譯成js的, 因為這個東西比較簡單, 應該不算泄露公司機密哇, 代碼的最后有幾個使用的實例, 你點擊運行即可查看, 有興趣自己寫一個延遲對象, 印象會更加深刻:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>無標題文檔</title>
</head>
<body>
<script>

//d為目標對象, b為一個函數對象;
var __extends = this.__extends || function (d, b) {
    //繼承了靜態屬性
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    //繼承了原型
    __.prototype = b.prototype;
    d.prototype = new __();
};

//Promise這個構造器, Deferred繼承Promise;
var Promise = (function () {
    function Promise() {
    }
    /**
     * onDone/onFail 應該返回值(或拋出異常),即不應返回 undefined,忘記返回值通常是 Bug,因此會在控制臺給出警告。
     * 如果確實不需要返回值,可返回 null。
     */
    Promise.prototype.then = function (onDone, onFail) {
        return null;
    };
    Object.defineProperty(Promise.prototype, "status", {
        get: function () {
            return 0;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Promise.prototype, "result", {
        get: function () {
            return undefined;
        },
        enumerable: true,
        configurable: true
    });
    Promise.prototype.done = function (onDone) {
        return this;
    };
    Promise.prototype.fail = function (onFail) {
        return this;
    };
    Promise.prototype.progress = function (onProgress) {
        return this;
    };

    Promise.when = function (promises) {
        var allDone = new Deferred();
        if (!promises.length) {
            allDone.resolve([]);
            return allDone;
        }
        var resolved = 0;
        for (var i = 0; i < promises.length; i++) {
            promises[i].done(function (v) {
                ++resolved;
                if (resolved === promises.length && allDone.status === Promise.UNFULFILLED) {
                    var results = promises.map(function (p) {
                        return p.result;
                    });
                    allDone.resolve(results);
                }
            }).fail(function (e) {
                        if (allDone.status === Promise.UNFULFILLED)
                            allDone.reject(e); //TODO 此處i是無用的,怎么指示是哪一個promise的信息?
                    }).progress(function (v) {
                        if (allDone.status === Promise.UNFULFILLED) {
                            allDone.notify(v); //TODO 此處i是無用的,怎么指示是哪一個promise的信息?
                        }
                    });
        }
        return allDone;
    };

    Promise.UNFULFILLED = 0;
    Promise.RESOLVED = 1;
    Promise.REJECTED = 2;
    return Promise;
})();

var Deferred = (function (_super) {
    //繼承原型
    __extends(Deferred, _super);
    function Deferred() {
        //繼承
        _super.call(this);
        //成功的列表;
        this._onDones = null;
        //失敗的列表;
        this._onFails = null;
        //進度的回調列表;
        this._onProgresses = null;
        // 0 : 為解決, 1 : 已解決, 2 : 被拒絕了;
        this._status = Promise.UNFULFILLED;
        this._result = undefined;
        if (Deferred._DEBUG) {
            try  {
                throw new Error('Deferred constructor calling stack');
            } catch (e) {
                this._stack = e;
            };
        };
    };

    //直接通過訪問_status也行;
    Object.defineProperty(Deferred.prototype, "status", {
        get: function () {
            return this._status;
        },
        enumerable: true,
        configurable: true
    });

    //直接訪問實例的_result也行;
    Object.defineProperty(Deferred.prototype, "result", {
        get: function () {
            return this._result;
        },
        enumerable: true,
        configurable: true
    });

    //把callback的成功列表全部壓棧;
    Deferred.prototype.done = function (onDone) {
        if (this._status == Promise.UNFULFILLED) {
            this._onDones = this._onDones || [];
            this._onDones.push(onDone);
        //如果已經成功直接觸發成功回調, (這里要注意有種情況是“已經失敗”的姿態, 這個成功回調并不會入棧或者觸發);
        } else if (this._status == Promise.RESOLVED)
            this._emitEventDirectly(onDone);
        return this;
    };

    //把callback的失敗列表全部壓棧;
    Deferred.prototype.fail = function (onFail) {
        if (this._status == Promise.UNFULFILLED) {
            this._onFails = this._onFails || [];
            this._onFails.push(onFail);
            //如果已經失敗直接觸發失敗回調;
        } else if (this._status == Promise.REJECTED)
            this._emitEventDirectly(onFail);
        return this;
    };

    Deferred.prototype.progress = function (onProgress) {
        if (this._status == Promise.UNFULFILLED) {
            this._onProgresses = this._onProgresses || [];
            this._onProgresses.push(onProgress);
        }
        return this;
    };

    //then這個很重要, 他會重新返回包裝后的延遲對象, 也是延遲對象里面比較復雜的東西;
    //功過then可以實現鏈式調用, 實例如下:
    /*
    var df = new Deferred();
    df.then(function() {
        return 1
    }).then(function(arg) {
        console.log(arg); 
        return 2;
    }).then(function(arg){
        console.log(arg)
    });
    df.resolve();
*/
    Deferred.prototype.then = function (onDone, onFail) {
        var _this = this;
        var def = new Deferred();
        var result;

        //then返回的是新的延遲對象
        //done是為當前這個延遲對象添加延遲對象;
        this.done(function (data) {
            // 這個done很重要, 有三個判斷;
            // 如果有沒有onDone就直接為當前的resolve; 一個舊的derffer被resolve了那么新的deferred也resovle, resolve的參數為舊deferred的參數:實例如下
            /*
                var df = new Deferred();
                df.then().done(function(arg){
                    console.log(arg);
                });
                df.resolve("1");
            */

            // 如果有onDone, onDone的返回是非Deferred對象,通過resolve(返回值)閉包內的Deferred對象, 實例如下:
            /*
                var df = new Deferred();
                df.then(function() {return 2}).done(function(arg){
                    console.log(arg);
                });
                df.resolve();
            */

            // 如果有onDone, 而且onDone返回對象是Promise的實例, 那么為返回的這個promise添加一個onDone, 這個onDone添加閉包內部的promise對象的resolve, 實例如下:
            /*  
                var df = new Deferred();
                df.then(function() { 
                    var closureDf = new Deferred();
                    setTimeout(function(){ 
                        closureDf.resolve("haha") 
                    },4000); 
                    return closureDf.promise(); 
                }).done(function(arg){
                    console.log(arg);
                });
                df.resolve();
                
            */
            if (onDone) {
                try  {
                    result = onDone(data);
                    //主要是起到提示的作用;
                    _this._warnReturnValue(result);
                    if (result instanceof Promise) {
                        //result又是一個延遲對象的話, 當result發生resolve的時候,def也reslove
                        //result.done(def.resolve.bind(this)).fail(def.reject.bind(this)).progress(def.notify.bind(this));
                        //result.done(function(arg){ def.resolve(arg) }).fail(function(arg){ def.reject(arg) })
                        def._bindTo(result);
                        //
                        return result;
                    } else
                        //最好不要延遲對象套延遲對象, 會暈;
                        def.resolve(result);
                } catch (err) {
                    def.reject(err);
                }
            } else
                def.resolve(data);
        });
        this.fail(function (err) {
            if (onFail) {
                try  {
                    result = onFail(err);
                    _this._warnReturnValue(result);
                    if (result instanceof Promise) {
                        def._bindTo(result);
                        return result;
                    } else {
                        def.resolve(result);
                    }
                } catch (err2) {
                    def.reject(err2);
                }
            } else
                def.reject(err);
        });

        return def;
    };

    Deferred.prototype.resolve = function (data) {
        if (typeof data === 'undefined')
            console.warn('>>>> Deferred.resolve() received undefined, likely a bug');
        return this._emitEvent(data, Promise.RESOLVED);
    };

    Deferred.prototype.reject = function (err) {
        if (Deferred._DEBUG) {
            try  {
                throw new Error('Deferred.reject calling stack');
            } catch (e) {
                logw('rejected: Defered.constructor stack:\n' + (this._stack['stack'] || this._stack) + '\nrejected: Defered.rejected stack:\n' + (e['stack'] || e) + '\nrejected: reason stack:\n' + (err['stack'] || err));
            }
        }
        return this._emitEvent(err, Promise.REJECTED);
    };

    Deferred.prototype.notify = function (data) {
        return this._emitEvent(data);
    };

    //這個是觸發事件的裝飾者, 你要觸發失敗回調, 成功回調, 進度回調都需要走這邊, 只是穿進來的姿態不一樣而已;
    Deferred.prototype._emitEvent = function (data, status) {
        if (this._status != Promise.UNFULFILLED) {
            throw Error('fulfilled');
        }

        var callbacks;
        //處理事件列表;
        if (status === Promise.RESOLVED)
            callbacks = this._onDones;
        else if (status === Promise.REJECTED)
            callbacks = this._onFails;
        else
            callbacks = this._onProgresses;

        //沒有status是onProgress的情況;
        if (status) {
            this._status = status;
            this._result = data;
            this._onDones = this._onFails = this._onProgresses = null;
        };

        if (callbacks) {
            for (var i = 0; i < callbacks.length; i++) {
                try  {
                    //這個是執行回調列表, 是線性的;
                    callbacks[i](data);
                } catch (e) {
                    this._log(e);
                }
            }
        }
        return this;
    };

    Deferred.prototype._bindTo = function (p) {
        p.done(this.resolve.bind(this)).fail(this.reject.bind(this)).progress(this.notify.bind(this));
    };

    Deferred.prototype._emitEventDirectly = function (callback) {
        var _this = this;
        if (!callback)
            return;
        setTimeout(function () {
            try  {
                callback(_this._result);
            } catch (e) {
                _this._log(e);
            }
        }, 0);
    };

    Deferred.prototype._log = function (err) {
        console.warn(err.stack || err);
    };

    Deferred.prototype._warnReturnValue = function (value) {
        if (typeof value === 'undefined')
            console.warn('>>>> Promise.then(): onDone/onFail returns undefined, likely a bug');
        else if (value && !(value instanceof Promise) && typeof value.then === 'function')
            console.warn('>>>> Promise.then(): onDone/onFail returns a promise-like object, likely a bug. Consider Promise.wrap().');
    };
    Deferred._DEBUG = false;
    return Deferred;
})(Promise);


//使用方法:
var df = new Deferred();
df.then(function() {
    return 1
}).then(function(arg) {
    console.log(arg); 
    return 2;
}).then(function(arg){
    console.log(arg)
});
df.resolve();

//單純的Deferred相對于一個callbacks, 使用then方法就變成了鏈式調用(個人理解);
var df = new Deferred();
df.then().done(function(arg){
    console.log(arg);
});
df.resolve("1");

//歡迎拍磚;
var df = new Deferred();
df.then(function() {return 2}).done(function(arg){
    console.log(arg);
});
df.resolve();
</script>
</body>
</html>

 

 

   jQ1.7的延遲對象, 代碼量比較少, 依賴callbacks,不太好懂, 慢慢看,參考jQ的延遲對象分析: http://www.cnblogs.com/snandy/archive/2012/12/19/2812935.html

 

   要先弄懂callbacks這個方法, 才能在弄懂Deferred,  when也只是一個Deferred的包裝。 建議先看1.5版本的Deferred, 弄懂了再看1.7的Deferred, 下面的Deferred被我單獨裁出來了,可以單獨運行, 里面的注釋是博客園的好像是aaron寫的代碼解析, 輔助大家看一下:

<!DOCTYPE html>
<html>
<head>
    <title></title>
</head>
<body>
<script>
//jQ部分的延遲對象截取;
jQuery = $ = {};
jQuery.extend = function() {
    var options, name, src, copy, copyIsArray, clone,
            target = arguments[0] || {},
            i = 1,
            length = arguments.length,
            deep = false;

    // Handle a deep copy situation
    if ( typeof target === "boolean" ) {
        deep = target;
        target = arguments[1] || {};
        // skip the boolean and the target
        i = 2;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
        target = {};
    }

    // extend jQuery itself if only one argument is passed
    if ( length === i ) {
        target = this;
        --i;
    }

    for ( ; i < length; i++ ) {
        // Only deal with non-null/undefined values
        if ( (options = arguments[ i ]) != null ) {
            // Extend the base object
            for ( name in options ) {
                src = target[ name ];
                copy = options[ name ];

                // Prevent never-ending loop
                if ( target === copy ) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays
                if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
                    if ( copyIsArray ) {
                        copyIsArray = false;
                        clone = src && jQuery.isArray(src) ? src : [];

                    } else {
                        clone = src && jQuery.isPlainObject(src) ? src : {};
                    }

                    // Never move original objects, clone them
                    target[ name ] = jQuery.extend( deep, clone, copy );

                    // Don't bring in undefined values
                } else if ( copy !== undefined ) {
                    target[ name ] = copy;
                }
            }
        }
    }

    // Return the modified object
    return target;
};

var optionsCache = {};
var core_rspace = /\s+/;
var core_toString = Object.prototype.toString;
var class2type = {};
jQuery.each = function( obj, callback, args ) {
    var name,
            i = 0,
            length = obj.length,
            isObj = length === undefined || jQuery.isFunction( obj );

    if ( args ) {
        if ( isObj ) {
            for ( name in obj ) {
                if ( callback.apply( obj[ name ], args ) === false ) {
                    break;
                }
            }
        } else {
            for ( ; i < length; ) {
                if ( callback.apply( obj[ i++ ], args ) === false ) {
                    break;
                }
            }
        }

        // A special, fast, case for the most common use of each
    } else {
        if ( isObj ) {
            for ( name in obj ) {
                if ( callback.call( obj[ name ], name, obj[ name ] ) === false ) {
                    break;
                }
            }
        } else {
            for ( ; i < length; ) {
                if ( callback.call( obj[ i ], i, obj[ i++ ] ) === false ) {
                    break;
                }
            }
        }
    };
    return obj;
};
$.isFunction = function( obj ) {
    return jQuery.type(obj) === "function";
}
$.type = function( obj ) {
    return obj == null ?
            String( obj ) :
            class2type[ core_toString.call(obj) ] || "object";
};
jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) {
    class2type[ "[object " + name + "]" ] = name.toLowerCase();
});
// Convert String-formatted options into Object-formatted ones and store in cache
function createOptions( options ) {
    var object = optionsCache[ options ] = {};
    jQuery.each( options.split( core_rspace ), function( _, flag ) {
        object[ flag ] = true;
    });
    return object;
};
//$.Callbacks( 'once memory unique stopOnFalse'  );
jQuery.Callbacks = function( options ) {
    // Convert options from String-formatted to Object-formatted if needed
    // (we check in cache first)
    options = typeof options === "string" ?
            ( optionsCache[ options ] || createOptions( options ) ) :
            jQuery.extend( {}, options );

    var // Last fire value (for non-forgettable lists)
            memory,
    // Flag to know if list was already fired
            fired,
    // Flag to know if list is currently firing
            firing,
    // First callback to fire (used internally by add and fireWith)
            firingStart,
    // End of the loop when firing
            firingLength,
    // Index of currently firing callback (modified by remove if needed)
            firingIndex,
    // Actual callback list
            list = [],
    // Stack of fire calls for repeatable lists
            stack = !options.once && [],
    // Fire callbacks
            fire = function( data ) {
                //如果有memory我們會把傳進來的參數保存;
                memory = options.memory && data;
                //觸發的標志;
                fired = true;
                //如果有memory的callback對象執行過了, 會有firingStart;
                firingIndex = firingStart || 0;
                firingStart = 0;
                firingLength = list.length;
                //callback已經觸發過后的標志;
                firing = true;
                for ( ; list && firingIndex < firingLength; firingIndex++ ) {
                    //return false的時候就不走了
                    if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
                        memory = false; // To prevent further calls using add
                        break;
                    }
                };
                firing = false;
                if ( list ) {
                    if ( stack ) {
                        if ( stack.length ) {
                            fire( stack.shift() );
                        }
                    } else if ( memory ) {
                        list = [];
                    } else {
                        self.disable();
                    }
                }
            },
    //通過閉包, 保存局部變量, 返回self;
    // Actual Callbacks object
            self = {
                // Add a callback or a collection of callbacks to the list
                add: function() {
                    if ( list ) {
                        // First, we save the current length
                        var start = list.length;

                        // 如果傳進來的是[fn, fn1, fn2 , [fn3, fn4, fn5, fn6]]會把數組扁平化哦
                        // Array.prototype.concat.apply([],[1,2,3,[4,5]]); 你懂得....;
                        (function add( args ) {
                            jQuery.each( args, function( _, arg ) {
                                //
                                if ( jQuery.isFunction( arg ) && ( !options.unique || !self.has( arg ) ) ) {
                                    list.push( arg );
                                } else if ( arg && arg.length ) {
                                    // Inspect recursively
                                    add( arg );
                                }
                            });
                        })( arguments );
                        // Do we need to add the callbacks to the
                        // current firing batch?
                        // 對定制選項的額外處理;
                        if ( firing ) {
                            firingLength = list.length;
                            // With memory, if we're not firing then
                            // we should call right away
                        } else if ( memory ) {
                            firingStart = start;
                            fire( memory );
                        }
                    }
                    return this;
                },
                // Remove a callback from the list
                remove: function() {
                    //沒list就不玩了;
                    if ( list ) {
                        jQuery.each( arguments, function( _, arg ) {
                            var index;
                            while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
                                list.splice( index, 1 );
                                // Handle firing indexes
                                // 對定制選項的額外處理;
                                if ( firing ) {
                                    if ( index <= firingLength ) {
                                        firingLength--;
                                    }
                                    if ( index <= firingIndex ) {
                                        firingIndex--;
                                    }
                                }
                            }
                        });
                    }
                    return this;
                },
                // Control if a given callback is in the list
                has: function( fn ) {
                    return jQuery.inArray( fn, list ) > -1;
                },
                // Remove all callbacks from the list
                empty: function() {
                    list = [];
                    return this;
                },
                // Have the list do nothing anymore
                disable: function() {
                    list = stack = memory = undefined;
                    return this;
                },
                // Is it disabled?
                disabled: function() {
                    return !list;
                },
                // Lock the list in its current state
                lock: function() {
                    stack = undefined;
                    if ( !memory ) {
                        self.disable();
                    }
                    return this;
                },
                // Is it locked?
                locked: function() {
                    return !stack;
                },
                // Call all callbacks with the given context and arguments
                fireWith: function( context, args ) {
                    args = args || [];
                    args = [ context, args.slice ? args.slice() : args ];
                    if ( list && ( !fired || stack ) ) {
                        if ( firing ) {
                            stack.push( args );
                        } else {
                            fire( args );
                        }
                    }
                    return this;
                },
                // Call all the callbacks with the given arguments
                fire: function() {
                    self.fireWith( this, arguments );
                    return this;
                },
                // To know if the callbacks have already been called at least once
                fired: function() {
                    return !!fired;
                }
            };

    return self;
};

//接著, 來參考一下jQ的延遲對象 , 原文地址在:http://www.cnblogs.com/lovesueee/archive/2012/10/18/2730287.html;
jQuery.extend({

    Deferred: function( func ) {
        // 數據元組集
        // 每個元組分別包含一些與當前deferred相關的信息:
        // 分別是:觸發回調函數列表執行(函數名),添加回調函數(函數名),回調函數列表(jQuery.Callbacks對象),deferred最終狀態(第三組數據除外)
        // 總體而言,三個元組會有對應的三個callbacklist對應于doneList, failList, processList
        // 對于jQuery.Callbacks對象,可以看之前的文章http://www.cnblogs.com/lovesueee/archive/2012/10/18/2729829.html
        var tuples = [
                    // action, add listener, listener list, final state
                    [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                    [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                    [ "notify", "progress", jQuery.Callbacks("memory") ]
                ],
        // deferred的狀態,分為三種:pending(初始狀態), resolved(解決狀態), rejected(拒絕狀態)
                state = "pending",
        // promise對象,主要有兩點作用:
        // 1. 在初始化deferred對象時,promise對象里的方法都會被extend到deferred中去
        // 2. 那么,生成的deferred對象里必然引用了promise對象的promise方法,所以當調用deferred.promise()時,
        //    deferred對象會通過閉包返回promise對象,這就是所謂的受限制的deferred對象(用deferred2表示),因為相比之前,
        //    返回的deferred2不在擁有resolve(With), reject(With), notify(With)這些能改變deferred對象狀態并且執行callbacklist的方法了
                promise = {

                    // 返回閉包里的內部state(外部只讀)
                    state: function() {
                        return state;
                    },

                    // 同時在doneList和failList的list里添加回調函數(引用)
                    // 那么不論deferred最終狀態是resolved還是rejected, 回調函數都會被執行,這就是所謂的always
                    always: function() {
                        deferred.done( arguments ).fail( arguments );
                        return this;
                    },

                    // jQuery.then()會創建一個新的受限制的deferred對象
                    // 有點復雜,下面我有畫一個圖幫忙理解
                    then: function( /* fnDone, fnFail, fnProgress */ ) {
                        var fns = arguments;
                        // 創建新的受限制的deferred對象(稱作newDeferrred),并返回
                        // 利用返回的deferred對象就可以做很多事了,你懂的
                        return jQuery.Deferred(function( newDefer ) {
                            /*
                             var tuples = [
                             // action, add listener, listener list, final state
                             [ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
                             [ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
                             [ "notify", "progress", jQuery.Callbacks("memory") ]
                             ]
                             */
                            jQuery.each( tuples, function( i, tuple ) {
                                var action = tuple[ 0 ],
                                        fn = fns[ i ];
                                // >>>> 很重要的一點就是:then這個方法是新建一個deferred,然后把當前deferred的done, fail progress三個依次添加新deferred, 并把這個deferred返回; <<<<

                                // deferred[ done | fail | progress ] for forwarding actions to newDefer
                                // 分別為deferred的三個callbacklist添加回調函數,根據fn的是否是函數,分為兩種情況:
                                // 1.不是函數的情況(如值為undefined或者null等),直接鏈接到newDeferred的resolve(reject,notify)方法,也就是說
                                //   newDeferrred的執行依賴外層的調用者deferred的狀態或者說是執行動作(resolve還是reject或者是notify)
                                //   此時deferred.then()相當于將自己的callbacklist和newDeferred的callbacklist連接起來了,故可以在newDeferred
                                //   中大做文章
                                // 2.是函數的情況,根據返回值(稱作returnReferred)是否是deferred對象,又可以分為兩種情況:
                                //   2.1 返回值是deferred對象,那么在returnReferred對象的三個回調函數列表中添加newDeferred的resolve(reject,notify)方法
                                //       也就是說newDeferrred的執行依賴returnDeferred的狀態
                                //   2.2 返回值不是deferred對象,那么將返回值returned作為newDeferred的參數并將從外層deferred那邊的上下文環境作為newDeferred
                                //       的執行上下文,然后執行對應的回調函數列表,此時newDeferrred的執行依賴外層的調用者deferred的狀態

                                //deferred.done(fn), deferred.fail(fn), deferred.progress(fn);
                                deferred[ tuple[1] ]( jQuery.isFunction( fn ) ?
                                    //傳進來的是函數的情況下, 函數可能不返回, 也可能返回一個延遲對象;;
                                        function() {
                                            //這行傳進來的參數, 利用閉包, fn是這個閉包的變量;
                                            var returned = fn.apply( this, arguments );
                                            //又返回了一個延遲對象的話, 我們再把這個新的延遲對象的resolve和reject和notify放到這個deferred;
                                            if ( returned && jQuery.isFunction( returned.promise ) ) {
                                                returned.promise()
                                                        .done( newDefer.resolve )
                                                        .fail( newDefer.reject )
                                                        .progress( newDefer.notify );
                                            } else {
                                                //這個函數返回的不是延遲對象, 把這個fn的返回值作為參數觸發newDefer;
                                                newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] );
                                            }
                                        } :
                                        newDefer[ action ]
                                );
                            });
                            fns = null;
                        }).promise();
                    },

                    promise: function( obj ) {
                        return typeof obj === "object" ? jQuery.extend( obj, promise ) : promise;
                    }
                },
        // 實際返回的deferred對象
                deferred = {};

        // Keep pipe for back-compat
        // pipe和then引用同一個函數,所以功能是一樣的
        // 只不過通常的用法是:會用pipe進行filter操作
        promise.pipe = promise.then;

        // Add list-specific methods
        // 通過上面定義的數據元組集來擴展一些方法
        jQuery.each( tuples, function( i, tuple ) {
            //就是callback了;
            var list = tuple[ 2 ],
            //resolved, rejected
                    stateString = tuple[ 3 ];

            // promise[ done | fail | progress ] = list.add
            // 給上面的promise對象添加done,fail,process方法
            // 這三個方法分別引用三個不同jQuery.Callbacks對象的add方法(不是同一個引用),
            // 那么這三個方法的用途就是向各自的回調函數列表list(各自閉包中)中添加回調函數,互不干擾
            promise[ tuple[1] ] = list.add;

            // Handle state
            // 通過stateString有值這個條件,預先向doneList,failList中的list添加三個回調函數
            // doneList : [changeState, failList.disable, processList.lock]
            // failList : [changeState, doneList.disable, processList.lock]
            // changeState 指的是下面首先添加的一個改變deferred對象的匿名函數
            // 可以看的出: 不論deferred對象最終是resolve(還是reject),在首先改變對象狀態之后,都會disable另一個函數列表failList(或者doneList)
            // 然后lock processList保持其狀態,最后執行剩下的之前done(或者fail)進來的回調函數
            // 當然了,上述情況processList除外
            if ( stateString ) {
                // 一旦觸發就會把這個閉包的姿態字符串保存的state里面去;
                list.add(function() {
                    // state = [ resolved | rejected ]
                    state = stateString;

                    // [ reject_list | resolve_list ].disable; progress_list.lock
                }, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
            }

            // deferred[ resolve | reject | notify ] = list.fire
            // 給deferred對象添加resolve(With), reject(With), notify(With)方法
            // 這三個方法分別引用三個不同jQuery.Callbacks對象的fire方法;
            deferred[ tuple[0] ] = list.fire;
            deferred[ tuple[0] + "With" ] = list.fireWith;
        });

        // jQuery.extend( deferred, promise );
        // 將上面的promise對象extend進deferred中
        promise.promise( deferred );

        // Call given func if any
        // 如果調用jQuery.Deferred(func)指定了參數,那么調用func并設置func的上下文和參數均為deferred
        // 在jQuery.then()中有用到這一點
        if ( func ) {
            func.call( deferred, deferred );
        }

        // All done!
        // 返回最終的deferred對象
        return deferred;
    },

    //suborinate:部屬;部下,下級的意思,
    when: function( subordinate /* , ..., subordinateN */ ) {
        var i = 0,
        // 首先將arguments偽數組轉換為真正的數組
                resolveValues = core_slice.call( arguments ),
                length = resolveValues.length,

        // the count of uncompleted subordinates
        // jQuery.isFunction( subordinate.promise )用來判斷subordinate是否是deferred對象
        // 1. 在參數個數等于1的情況下:
        //   1.1 如果參數是deferred對象,那么remaining = length, 這是remaining就是1嘛
        //   1.2 否則remaining為0
        // 2. 在參數不等于1(即等于0或者大于1)的情況:remaining = length
                remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,

        // the master Deferred. If resolveValues consist of only a single Deferred, just use that.
        // 到這里就可以知道:如果參數個數僅為1個,并且是deferred對象,那么就無需再生成deferred對象
                deferred = remaining === 1 ? subordinate : jQuery.Deferred(),

        // Update function for both resolve and progress values
                updateFunc = function( i, contexts, values ) {
                    // 這里返回一個函數作為一個callback完全是為了創建一個閉包,主要是為了保持i的值
                    return function( value ) {
                        // 保存各個deferred執行的上下文,也就是說之后whenDeferred的回調函數的上下文就是一個數組
                        contexts[ i ] = this;
                        // 保存各個deferred執行時的參數,之后傳遞給whenDeferred的回調函數
                        // 此時values的值有原先的jQuery.when()傳進來的參數變為各個deferred執行回調時的參數了,也就是說覆蓋了
                        values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
                        if( values === progressValues ) {
                            deferred.notifyWith( contexts, values );
                            //所有的defer都執行了以后remaining就等于0了;
                        } else if ( !( --remaining ) ) {
                            // 時機成熟,即所有延遲都resolve,執行whenDeferred的回調函數
                            deferred.resolveWith( contexts, values );
                        }
                    };
                },

                progressValues, progressContexts, resolveContexts;

        // add listeners to Deferred subordinates; treat others as resolved
        // 如果參數個數大于1,那么就是說有可能存在多個deferred對象
        // 這時需要一些條件判斷以保證是所有的deferred對象都resolve了,再執行whenDeferred的resolve
        // 或者當有一個deferred對象reject了,whenDeferred的reject
        if ( length > 1 ) {
            progressValues = new Array( length );
            progressContexts = new Array( length );
            resolveContexts = new Array( length );
            for ( ; i < length; i++ ) {
                // 如果是deferred對象, 為每一個promise添加update,失敗就執行返回deferred的fail列表;
                if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
                    // 給每個參數(deferred對象)添加最后的回調,用來檢查此時的狀態

                    resolveValues[ i ].promise()
                        // 用于當每一個deferred對象resolve回來,用updateFunc返回的函數檢查此時其他deferred對象的狀態(即此時remaining是否等于0了)
                        //updateFunc是一個閉包, 他把i傳進去了, 只有then().done()或者then().promise()有返回值, promise(),或者是done是沒有返回值的;
                            .done( updateFunc( i, resolveContexts, resolveValues ) )
                        // 如果有一個deferred對象reject,whenDeferred將執行reject
                            .fail( deferred.reject )
                        //updateFunc又是一個閉包, ;
                            .progress( updateFunc( i, progressContexts, progressValues ) );
                    // 如果不是deferred對象,直接--remaining,視為resolve
                } else {
                    --remaining;
                };
            };
        };

        // if we're not waiting on anything, resolve the master
        // 如果此時remaining就等與0了,表示沒有什么延遲需要等待,那么立即之行whenDeferred的resolveWith
        // 此時resolveContexts為undefined, 這就意味這上下文將為全局的window
        if ( !remaining ) {
            deferred.resolveWith( resolveContexts, resolveValues );
        }

        // 返回promise對象;
        return deferred.promise();
    }
});

var df = $.Deferred();
df.done(function(arg){
    console.log(arg);
    console.log(1)
})
df.done(function(arg){
    console.log(arg);
    console.log(2)
});
df.resolve("Deffffffffffrrrrred");

//延遲對象pipe的使用, pipe和then是一樣的, 指向同一個函數;
var df = $.Deferred();
df.pipe(function() {
    var closureDf = $.Deferred();
    setTimeout(function(){
        closureDf.resolve("haha")
    },4000);
    console.log(closureDf);
    return closureDf;
}).done(function(arg){alert(1)
            console.log(arg);
        });
df.resolve();

</script>
</body>
</html>

 

   ES6原生的好像支持PROMISE么么噠,O(∩_∩)O哈哈~;

  
參考鏈接:
  用法:
    阮一峰的jQuery.Deferred對象: http://javascript.ruanyifeng.com/jquery/deferred.html

  Aaron:

    Aaron深入分析延遲對象:http://www.cnblogs.com/aaronjs/p/3356505.html

    Aaron  分析 Deferred概念 :http://www.cnblogs.com/aaronjs/p/3348569.html
  github:
    when的主頁: https://github.com/cujojs/when/
    延遲對象cho45: https://github.com/cho45/jsdeferred
  學習實例:
    whenJS的使用方法,來自蛐蛐的個人博客:https://www.imququ.com/post/promises-when-js.html
    教你一步一步實現一個Promise:http://www.cnblogs.com/ygm125/p/3735677.html
  百科:
    promise規范A+: https://promisesaplus.com/
    維基百科的promise/A規范說明:http://wiki.commonjs.org/wiki/Promises/A  ,打開非常慢
    維基百科的promise/B規范說明:http://wiki.commonjs.org/wiki/Promises/B ,打開也非常慢

  


文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()