1. 程式人生 > >實現一個自己的promise

實現一個自己的promise

這是小弟的一篇開篇小作,如有不當之處,請各位道友批評指正。本文將探討Promise的實現。

一、ES6中的Promise

1、簡介

據說js很早就實現了Promise,我是不知道的,我第一次接觸Promise就是在ES6中。Promise就是規定在未來達到某個狀態時應該採取某種行動,而這種未來的狀態是不確定的。阮一峰說Promise物件用來傳遞非同步訊息,代表了某個未來才會知道結果的事件,併為這個事件提供統一的API,供進一步處理。
Promise和事件有本質區別:Promise是用一次就扔,而事件可以被多次觸發;Promise必定會產生一個訊號,要麼resolved(fulfilled),要麼rejected,而事件可能不被觸發。

2、基本用法

var p = new Promise(function(resolve){
    setTimeout(function(){
        resolve(111);
    },1000)
});

p.then(function(value){
    console.log("承諾解決了,拿到的資料為:"+value);
});
上面建立了一個promise,1秒後解決,然後用then方法添加了狀態改變的回撥函式。then方法中可以指定兩個函式,第一個位承諾是變成resolved狀態的回撥函式,第二個是承諾變為rejected狀態的回撥函式(可省略)。也可以在catch方法中指定承諾變為rejected的函式。如下:
p.catch(function(error){
    console.log("rejected callback");
})

3、Promise.all()的用法

它接受一個promise物件陣列為引數,當陣列中的每一個promise都變成resolved狀態時,整個才算為resolved,陣列中promise的返回值將組成一個數組傳遞給Promise.all()返回的promise的回撥函式。陣列中的只要有一個為rejected整個為rejected;
var p1 = new Promise(function(resolve){
        resolve("p1 resolved"
); }); var p2 = new Promise(function(resolve){ resolve('p2 resolved'); }); var p3 = new Promise(function(resolve){ resolve("p3 resolved"); }); var p = Promise.all([p1,p2,p3]);//這裡得到了一個新的promise p.then(function(value){ console.log("p resolved, 得到的引數為:"+value); }) .catch(function(error){ console.log("p rejected,"+error); }); //output:p resolved, 得到的引數為:p1 resolved,p2 resolved,p3 resolved
將p2改為rejected
var p1 = new Promise(function(resolve){
        resolve("p1 resolved");
});
var p2 = new Promise(function(resolve,reject){
        reject('p2 rejected');
});
var p3 = new Promise(function(resolve){
        resolve("p3 resolved");
});


p = Promise.all([p1,p2,p3]);
p.then(function(value){
    console.log("p resolved, 得到的引數為:"+value);
})
.catch(function(error){
    console.log("p rejected,"+error);
});
//output:p rejected,p2 rejected

4、Promise.race()的用法

它和Promise.all()的引數相同,它返回的promise的狀態隨著promise陣列中第一個狀態發生改變的promise的狀態而改變。
var p1 = new Promise(function(resolve){
        resolve("p1 resolved");
});
var p2 = new Promise(function(resolve,reject){
        reject('p2 rejected');
});
var p3 = new Promise(function(resolve){
        resolve("p3 resolved");
});


p = Promise.race([p1,p2,p3]);
p.then(function(value){
    console.log("p resolved, 得到的引數為:"+value);
})
.catch(function(error){
    console.log("p rejected,"+error);
});
//output:p resolved, 得到的引數為:p1 resolved
我們發現雖然p2是rejected,但是p是resolved,因為第一個狀態變化的是p1,而p1是resolved。如果p1是rejected,那麼p一定是rejected。
上面就是ES6原生支援的Promise,那麼我們該如何實現一個類似的自己的Promise呢?都原生支援了為什麼還要自己實現呢?第一它是一種樂趣。第二它可以幫助我們更好的認識釋出訂閱模式。下面開始正式戰鬥!(有木有很激動,終於要開始了)。

二、實現自己的Promise

1、基礎實現

function Promise(fn) {
    var value = null,
        deferreds = [];
    this.then = function (onFulfilled) {
        deferreds.push(onFulfilled);
    };
    function resolve(value) {
        deferreds.forEach(function (deferred) {
            deferred(value);
        });
    }
    fn(resolve);
}
這個建構函式傳進來一個function,聲明瞭倆個內部變數,value傳到其內部方法resolve,defferreds儲存回撥函式,每次呼叫例項的then方法會讓then中的函式入隊,其內部方法resolve接受一個引數,它遍歷了defferreds佇列,並執行其中的方法。這個內部方法被傳遞給了建構函式的引數fn。結合ES6中Promise的基本用法應該不難理解這段程式碼。其then方法相當於是一個訂閱過程,resolve方法相當於一個釋出過程。
var mypromise  = new Promise(function(resolve){
    setTimeout(function(){
        resolve("qqq");
    },1000)
});

mypromise.then(function(value){
    console.log(value); //qqq
})

2、問題修復

上述Promise的問題是,如果傳進去的不是一個非同步函式,那麼resolve方法會先執行,此時還沒有呼叫then,也就是說還沒有人訂閱,defferreds佇列還是空的,不合預期。改進如下:
function Promise(fn) {
    var value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        deferreds.push(onFulfilled);
    };

    function resolve(value) {
    //這裡將resolve放到了棧底,所以then會先執行(如果有then的話)
        setTimeout(function () {    
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }
    fn(resolve);
}

3、考慮一種情況:如果回撥函式註冊的很晚會怎麼樣

var mypromise  = new Promise(function(resolve){
        resolve("qqq");
});
setTimeout(function(){
   mypromise.then(function(value){
    console.log(value);
}) 
},1000);
結果是啥也沒做,為什麼呢?因為當我們註冊回撥的時候resolve已經執行了。那可咋整呢?解決方法就是記住Promise的狀態。請看程式碼:
function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled) {
        if (state === 'pending') {
            deferreds.push(onFulfilled);
            return this;
        }
        onFulfilled(value);
        return this;
    };

    function resolve(newValue) {
        value = newValue;
        state = 'fulfilled';
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                deferred(value);
            });
        }, 0);
    }

    fn(resolve);
}
就是這麼簡單,我們為Promise增加了一個內部變數state儲存其狀態,初始為pending(等待的意思),當resolve時,改變其狀態為fulfilled,調then的時候做個判斷,如果是pending說明resolve方法還沒執行,那麼我們將回調函式加到佇列等待resolve即可,如果是fulfilled,說明resolve已經執行,那麼我們直接執行新加入的回撥函式。至於那個return this,如果你用過jquery就知道了。

4、Promise鏈

我們考慮這樣的情況
var mypromise  = new Promise(function(resolve){
        resolve("first promise");
});
mypromise.then(function(value){
    console.log(value);
    return new Promise(function(resolve){
        resolve("second promise");
    })
})
.then(function(v){
    console.log(v);
}) 
//結果:first promise
//     first promise
//為啥是兩個first promise,這個是必然的,好好想想。第一個then返回的是前一個promise,而不是新建立的promise。
下面做一件有難度的是,我們來實現promise鏈,我看了幾個小時才搞明白。不要怕,打起精神來,我們開始。
this.then = function (onFulfilled) {
    return new Promise(function (resolve) {
        handle({
            onFulfilled: onFulfilled || null,
            resolve: resolve
        });
    });
};

function handle(deferred) {
    if (state === 'pending') {
        deferreds.push(deferred);
        return;
    }

    var ret = deferred.onFulfilled(value);
    deferred.resolve(ret);
}

function resolve(newValue) {
    if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
        var then = newValue.then;
        if (typeof then === 'function') {
            then.call(newValue, resolve);
            return;
        }
    }
    state = 'fulfilled';
    value = newValue;
    setTimeout(function () {
        deferreds.forEach(function (deferred) {
            handle(deferred);
        });
    }, 0);
}
then方法中返回了一個新的promise例項,在新例項中調了一個內部方法,傳進去一個回撥函式和一個resolve方法構成的物件;內部方法判斷如果當前promise沒有調resolve的話,將傳入的物件入隊,否則的話直接調傳入的回撥函式,將回調函的返回值交給resolove方法。想一下,我們的回撥函式會返回什麼值,可能是一個promise物件也可能是字串或者undefined。如果說返回了一個物件,證明使用者又返回了一個promise,此時我們將使用者返回的promise的then拿到,然後調這個then,如果不是個物件或者函式,證明使用者沒有返回新的promise,此時直接resolve。

5、拒絕狀態

前面講了resolve,reject就是手到禽來了。
function Promise(fn) {
    var state = 'pending',
        value = null,
        deferreds = [];

    this.then = function (onFulfilled, onRejected) {
        return new Promise(function (resolve, reject) {
            handle({
                onFulfilled: onFulfilled || null,
                onRejected: onRejected || null,
                resolve: resolve,
                reject: reject
            });
        });
    };

    function handle(deferred) {
        if (state === 'pending') {
            deferreds.push(deferred);
            return;
        }

        var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
            ret;
        if (cb === null) {
            cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
            cb(value);
            return;
        }
        ret = cb(value);
        deferred.resolve(ret);
    }

    function resolve(newValue) {
        if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
            var then = newValue.then;
            if (typeof then === 'function') {
                then.call(newValue, resolve, reject);
                return;
            }
        }
        state = 'fulfilled';
        value = newValue;
        finale();
    }

    function reject(reason) {
        state = 'rejected';
        value = reason;
        finale();
    }

    function finale() {
        setTimeout(function () {
            deferreds.forEach(function (deferred) {
                handle(deferred);
            });
        }, 0);
    }

    fn(resolve, reject);
}

6、處理resolve和reject的回撥函式異常

//改造了內部函式handle
function handle(deferred) {
    if (state === 'pending') {
        deferreds.push(deferred);
        return;
    }

    var cb = state === 'fulfilled' ? deferred.onFulfilled : deferred.onRejected,
        ret;
    if (cb === null) {
        cb = state === 'fulfilled' ? deferred.resolve : deferred.reject;
        cb(value);
        return;
    }
    try {
        ret = cb(value);
        deferred.resolve(ret);
    } catch (e) {
        deferred.reject(e);
    } 
}
 真是無語,我已經寫完了,Promise.all()和Promise.race()的實現和總結都寫好了。已為新增參考文獻是那個引用,按了快捷鍵Ctrl+Q,結果退出了瀏覽器,氣死了。不寫了,大家可以參考文章最後的參考文獻,不過那個race方法的實現可能還有點問題,返回的promise的狀態不是跟第一個陣列中狀態發生改變的promise的狀態一致,而是最後一個。

三、總結

第一次寫部落格,突然那些寫了那麼多優秀文章的作者表示由衷佩服。這個Promise的實現確實是非常巧妙的,如果真的要我獨自實現,恐怕還需要一些時日。不管怎樣,終於完成了我的開篇之作。寫作過程中對Promise的實現有了進一步認識。最後希望自己能夠堅持寫部落格,在寫作中學習。

參考文獻: