深入理解promise對象

前言

js中的異步,剛開始的時候都是用回調函數實現的,所以如果異步嵌套的話,就有出現回調地獄,使得代碼難以閱讀和難以維護,後來es6出現了promise,解決了回調地獄的問題。現在我們就自己寫代碼實現一下promise,這樣才能深入理解promise的運行機制,對以後使用promise也能夠更加得心應手。開始之前可以先看下promise的官網
promise/A+

先來看下promise的用法

new Promise((resolve,reject)=>{
  resolve(1);
  reject(11);
}).then(res=>{
  console.log(res);
  setTimeout(()=>{
    return new Promise((resolve,reject)=>{
        resolve(2)
    }) 
  },1000)
}).then(res2=>{
  console.log(res2);
});

控制檯打印
1
...1s later
2

先分析下上面這段代碼,先提出幾個問題
1.第一段resolve和reject都有,但是隻輸出了1,爲什麼?
2.then裏的res是如何取到resolve中的值的?
3.promise是如何做到鏈式調用的?

狀態機

promise中有個狀態機的概念,先說下爲什麼要有狀態機的概念呢,因爲promise的狀態是單向變化的,有三種狀態,pending,fullfilled,rejected,而這三種狀態只能從pending->fullfilled或者pending->rejected這兩種形式,也就是說執行了fullfilled之後,就不會執行rejected。這就解釋了上面的第一個問題。

下面我們來看下具體實現的完整代碼

const PENDING = 'PENDING';
const FULLFILLED = 'FULLFILLED';
const REJECTED = 'REJECTED';    
class Promise{
    constructor(fn){
       this.status = PENDING;//狀態
        this.data = undefined;//返回值
        this.defercb = [];//回調函數數組
        //執行promise的參數函數,並把resolve和reject的this綁定到promise的this
        fn(this.resolve.bind(this),this.reject.bind(this));
    }
    resolve(value){
        if(this.status === PENDING){
            //只能pending=>fullfied
            this.status = FULLFILLED;
            this.data = value;
            this.defercb.map(item=>item.onFullFilled());
        }
    }
    reject(value){
        if(this.status === PENDING){
            //只能pending=>rejected
            this.status = REJECTED;
            this.data = value;
            this.defercb.map(item=>item.onRejected());
        }
    }
    then(resolveThen,rejectThen){
       //如果沒有resolveThen方法,保證值可以穿透到下一個then裏有resolveThen的方法中
        resolveThen = typeof resolveThen === 'function' ? resolveThen : function(v) {return v};
        rejectThen = typeof rejectThen === 'function' ? rejectThen : function(r) {return r};
        //返回的都是promise對象,這樣就可以保證鏈式調用了
        switch(this.status){
            case PENDING:
            return new Promise((resolve,reject)=>{
                const onFullFilled = () => {
                    const result = resolveThen(this.data);//這裏調用外部then的resolveThen方法,將值傳回去
                    //如果返回值是promise對象,執行then方法,取它的結果作爲新的promise實例的結果,因爲this.data會重新賦值
                    result instanceof Promise && result.then(resolve,reject);
                }
                const onRejected = ()=>{
                    const result = rejectThen(this.data);
                    result instanceof Promise && result.then(resolve,reject);
                }
                this.defercb.push({onFullFilled,onRejected});
            });
            break;
            case FULLFILLED:
               return new Promise((resolve,reject)=>{
                     const result = resolveThen(this.data);
                     result instanceof Promise && result.then(resolve,reject);
                     resolve(result);
               })
            break;
            case REJECTED:
               return new Promise((resolve,reject)=>{
                   const result = rejectThen(this.data);
                   result instanceof Promise && result.then(resolve,reject);
                   reject(result)
               })   
            break;
        }
    }
}

運行下面的例子

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve(1);
    }, 1000);
}).then((res2) => {
    console.log(res2);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(2);
        }, 1000);
    });
}).then((res3) => {
    console.log(res3);
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve(3);
        }, 1000);
    });
}).then((res4) => {
    console.log(res4);
});

控制檯打印
...1s later
1
...1s later
2
...1s later
3
說明上面的實現是沒有問題的
不過還有一個問題,就是事件循環的順序問題,比如執行下面的代碼

new Promise((resolve) => {
    resolve();
})
.then(() => {
    console.log('1');
})
.then(() => {
    console.log('2');
});
console.log('3');

並沒有像預想中輸出3,1,2,而是輸出了1,2,3,原因就是因爲我們的這個Promise是在主線程中,沒有在下一個任務隊列中,可以加上settimeout解決這個問題,不過這也只是爲了讓我們更好理解執行順序而已,然而實際上是promise是屬於微任務中的,而settimeout是屬於宏任務,還是不太一樣的

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章