2017年1月25日 星期三

(js)Promise_好版本

原出處


module.exports = Promise;

var PENDING = undefined,
    FULFILLED = 1,
    REJECTED = 2;


function Promise(_resolver, name) {
    debugger;
    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);
    }
    this._name = name || 'then';
    this.guid = Promise.prototype.guid++;
    /* ---------------------------------- */
    var promise = this;
    promise._value;
    promise._reason;
    promise._status = PENDING;
    /* ---------------------------------- */
    promise._resolves = [];
    promise._rejects = [];
    /* ---------------------------------- */
    debugger;
    // 執行賦予的主任務
    _resolver(resolve, reject);

    return;
    /* ---------------------------------- */

    function resolve(value) {
        debugger;
        transition.apply(promise, [FULFILLED, value]);
    }

    function reject(reason) {
        debugger;
        transition.apply(promise, [REJECTED, reason]);
    }
};
/* ========================================================================== */
Promise.prototype.guid = 0;

/* ========================================================================== */
Promise.prototype.then = function(onFulfilled, onRejected) {
    debugger;
    var promise = this;
    // 每次返回一个promise,保证是可thenable的
    return new Promise(function(resolve, reject) {
        debugger;

        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);
        }

        return;
        /* ---------------------------------------- */

        function callback(value) {
            debugger;
            // 執行(onFulfilled)
            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) {
            debugger;
            reason = isFunction(onRejected) && onRejected(reason) || reason;
            reject(reason);
        }
    });
};
/* ========================================================================== */
Promise.prototype.catch = function(onRejected) {
    return this.then(undefined, onRejected)
};
/* ========================================================================== */
Promise.prototype.delay = function(ms, val) {
    return this.then(function(ori) {
        return Promise.delay(ms, val || ori);
    })
};
/* ========================================================================== */
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,
            count = len

        function resolver(index) {
            return function(value) {
                resolveAll(index, value);
            };
        }

        function rejecter(reason) {
            reject(reason);
        }

        function resolveAll(index, value) {
            result[index] = value;
            if (--count == 0) {
                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);
        }
    });
};

////////////////////////////////////////////////////////////////////////////////
function isFunction(obj) {
    return 'function' === typeof obj;
};
/* ========================================================================== */
function isArray(obj) {
    return Object.prototype.toString.call(obj) === "[object Array]";
};
/* ========================================================================== */
/**
 * 若對象含有.then()
 */
function isThenable(obj) {
    return obj && typeof obj['then'] == 'function';
};
/* ========================================================================== */
function transition(status, value) {
    debugger;
    var promise = this;

    if (promise._status !== PENDING) {
        return;
    }

    console.log('------------------');
    console.log('name = %s, guid = %d', this._name, this.guid);
    console.dir(this._resolves);
    console.dir(this._rejects);
    console.log('------------------');

    // 所以的执行都是异步调用,保证then是先执行的
    // 存疑的步驟
    setTimeout(function() {
        debugger;
        console.log('do mission: name = %s, guid = %d', promise._name, promise.guid);
        promise._status = status;

        // 執行任務
        publish.call(promise, value);
    }, 0);
};
/* ========================================================================== */
/**
 * 執行(._resolves)or(_rejects)
 */
function publish(val) {
    debugger;

    var promise = this,
        fn,
        st = promise._status === FULFILLED;

    // 任務隊列
    var queue = promise[st ? '_resolves' : '_rejects'];

    while (fn = queue.shift()) {
        val = fn.call(promise, val) || val;
    }
    promise[st ? '_value' : '_reason'] = val;

    promise['_resolves'] = undefined;
    promise['_rejects'] = undefined;
}
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

教你一步步實現一個Promise

Promise我想現在大家都非常熟悉了,主要作用就是解決異步回調問題,這裡簡單介紹下。
Promise規範是CommonJS規範之一,而Promise規範又分了好多種,比如 Promises/A、Promises/B、Promises/Kiss等等
有興趣的可以到這多瞭解一些 http://wiki.commonjs.org/wiki/Promises
現在比較流行的是Promise/A規範,人們對它的完善和擴展,逐漸形成了Promise/A+規範,A+已脫穎而出。
說到這裡規範是什麼,可以去這裡瞭解下
現在已有瀏覽器內置支持Promise,它的api語法可以在這裡查看
可以看到它的api並不多,其實規範也不多,我覺的大致抓住幾個重要的點就夠了,
1、promise有三種狀態,等待(pending)、已完成(fulfilled)、已拒絕(rejected)
2、promise的狀態只能從「等待」轉到「完成」或者「拒絕」,不能逆向轉換,同時「完成」和「拒絕」也不能相互轉換
3、promise必須有一個then方法,而且要返回一個promise,供then的鏈式調用,也就是可thenable的
4、then接受倆個回調(成功與拒絕),在相應的狀態轉變時觸發,回調可返回promise,等待此promise被resolved後,繼續觸發then鏈
知道這幾個重要的特點,我們就可以參考瀏覽器內置的api來實現了,
我們可以不必太受規範約束,先按照自己的想法來就好了。
promise的使用大致如下
var promise = new Promise(function(resolve, reject) {
    setTimeout(function(){
        resolve("val")
    });
});
promise.then(onFulfilled,onRejected).then(onFulfilled,onRejected)
主要思路就是我們可以直接對返回的promise對象進行操作,比如then,傳入回調,
這裡的函數並不會立即執行,而是加入隊列,等待未來的某個時間resolve時被觸發執行。
有了以上說明,就可以來實現了
首先定義三個狀態
var PENDING = undefined, FULFILLED = 1, REJECTED = 2;
然後實現Promise構造函數,此函數接受一個函數參數,函數參數接受倆個我們提供的方法resolve與reject,
供使用者在未來的某個時間裡調用觸發執行我們的隊列,這裡還要初始下當前的狀態,傳遞的值,
以及then時保存到的隊列。
大概像下面這樣
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){
        //狀態轉換為FULFILLED 
        //執行then時保存到_resolves裡的回調,
        //如果回調有返回值,更新當前_value
    }
    var reject = function(reason){
        //狀態轉換為REJECTED
        //執行then時保存到_rejects裡的回調,
        //如果回調有返回值,更新當前_rejects
    }

    resolver(resolve,reject);
}
有了這個,我們在實現一個then就ok了,
then裡要做的就是返回一個promise供then的鏈式調用,
而且promise.then(onFulfilled,onRejected)時,我們要判斷當前promise的狀態,
如果是pending則把onFulfilled與onRejected添加到_resolves與_rejects數組裡,
否則的話根據狀態,直接觸發回調,這裡要注意的是,如果返回的是promise,我們要等到此promise被resolves時,觸發then鏈的下一個promise執行。
代碼大概是這樣
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)){
              // 根據返回的promise執行的結果,觸發下一個promise相應的狀態
            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);
           }
    });
}
這裡說明下
var isThenable = function(obj){
  return obj && typeof obj["then"] == "function";
}
也就是說返回的對象帶有then方法,我們就當作promise對象
到這裡我們主要的工作就完成了,其他的all,race等方法都很簡單,具體可以到這裡看完整的實現
下面我們來做幾個例子來看下效果
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
});
當然可以直接getData100().then(getData200).then(function(val){})
then可以只傳一個,接受成功的回調,也可以用catch方法,接受失敗的回調,
catch是then的一個語法糖,相當於promise.then(undefined, onRejected)
也可以用all來並行執行
Promise.all([getData100(),getData200()]).then(function(value){
    console.log(value) // ["100ms","200ms"]
});
結果的順序與傳入的順序相同。
我們也可以直接創建一個以obj為值的成功狀態的promise,
Promise.resolve("FULFILLED").then(function(val){
    console.log(val) // FULFILLED
});
實現都相當簡單,看代碼就懂。
這裡也可以做一些好玩的,比如創建一個delay方法
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.delay(1000).then(function(){
    // 一些操作
}).delay(1000).then(function(){
    // 一些操作
})
我們也可以包裝一個循環,執行多少次,每次延遲多少秒執行什麼操作
var len = 0,
    words = "你好,你是誰?";

function count(num,ms,cb){
    var pro = Promise.resolve();
    for (var i = 0; i < num; i++) {
        pro = pro.delay(ms).then(function(v){
            return cb(v);
        });
    };
}

count(words.length,800,function(){
    var w = words.substr(0,++len);
    console.log(w);
})
更多的東西等你來實現~