對Promise源碼的理解

前言

Promise做爲一種異步處理的解決方案,以同步的寫做方式來處理異步代碼。本文只涉及Promise函數和then方法,對其餘方法(如catch,finally,race等)暫不研究。首先,看下Promise的使用場景。ajax

使用場景

例1 普通使用數組

new Promise((resolve, reject) => {
    console.log(1);
    resolve(2);
    console.log(3);
    }).then(result => {
         console.log(result);
    }).then(data => {
         console.log(data);
    });

// =>  1
// =>  3
// =>  2
// =>  undefined

構造函數Promise接受一個函數參數exactor,這個函數裏有兩個函數參數(resolve和reject),在實例化以後,當即執行這個exactor,須要注意的是,exactor裏面除了resolve和reject函數都是異步執行的,其餘都是同步執行。promise

經過它的then方法【註冊】promise異步操做成功時執行的回調,意思就是resolve傳入的數據會傳遞給then中回調函數中的參數。能夠理解爲,先把then中回調函數註冊到某個數組裏,等執resolve時候,再執行這個數組中的回調函數。若是then中的回調函數沒有返回值,那麼下個then中回調函數的參數爲undefined。 then方法註冊回調函數,也能夠理解爲【發佈訂閱模式】。緩存

例2 Promise與原生ajax結合使用異步

function getUrl(url) {
    return new Promise((resolve, reject) => {
        let xhr = new XMLHttpRequest()
        xhr.open('GET', url, true);
        xhr.onload = function () {
          if (/^2\d{2}$/.test(this.status) || this.status === 304 ) {
               resolve(this.responseText, this)
          } else {
               let reason = {
                    code: this.status,
                    response: this.response
               };
               reject(reason, this);
          }
        };
        xhr.send(null);
    });
}

getUrl('./a.text')
.then(data => {console.log(data)});

例3 Promise與$.ajax()結合使用函數

var getData=function(url) {
    return new Promise((resolve, reject) => {
      $.ajax({ 
          type:'get', 
          url:url, 
          success:function(data){ 
              resolve(data);
          }, 
          error:function(err){ 
              reject(err);
          } 
        });
    });
}
getData('./a.txt')
.then(data => {
    console.log(data);
});

Promise源碼分析

首先看一下Promise函數的源碼源碼分析

function Promise(excutor) {
    let that = this; // 緩存當前promise實例對象
    that.status = PENDING; // 初始狀態
    that.value = undefined; // fulfilled狀態時 返回的信息
    that.reason = undefined; // rejected狀態時 拒絕的緣由
    that.onFulfilledCallbacks = []; // 存儲fulfilled狀態對應的onFulfilled函數
    that.onRejectedCallbacks = []; // 存儲rejected狀態對應的onRejected函數

    function resolve(value) { // value成功態時接收的終值
        if(value instanceof Promise) {
            return value.then(resolve, reject);
        }

        // 爲何resolve 加setTimeout?
        // 2.2.4規範 onFulfilled 和 onRejected 只容許在 execution context 棧僅包含平臺代碼時運行.
        // 注1 這裏的平臺代碼指的是引擎、環境以及 promise 的實施代碼。實踐中要確保 onFulfilled 和 onRejected 方法異步執行,且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行。

        setTimeout(() => {
            // 調用resolve 回調對應onFulfilled函數
            if (that.status === PENDING) {
                // 只能由pedning狀態 => fulfilled狀態 (避免調用屢次resolve reject)
                that.status = FULFILLED;
                that.value = value;
                that.onFulfilledCallbacks.forEach(cb => cb(that.value));
            }
        });
    }

    function reject(reason) { // reason失敗態時接收的拒因
        setTimeout(() => {
            // 調用reject 回調對應onRejected函數
            if (that.status === PENDING) {
                // 只能由pedning狀態 => rejected狀態 (避免調用屢次resolve reject)
                that.status = REJECTED;
                that.reason = reason;
                that.onRejectedCallbacks.forEach(cb => cb(that.reason));
            }
        });
    }

    // 捕獲在excutor執行器中拋出的異常
    // new Promise((resolve, reject) => {
    //     throw new Error('error in excutor')
    // })
    try {
        excutor(resolve, reject);
    } catch (e) {
        reject(e);
    }
}

根據上面代碼,Promise至關於一個狀態機,一共有三種狀態,分別是 pending(等待),fulfilled(成功),rejected(失敗)。ui

that.onFulfilledCallbacks這個數組就是存儲then方法中的回調函數。執行reject函數爲何是異步的,就是由於裏面有setTimeout這個函數。當reject執行的時候,裏面的 pending狀態->fulfilled狀態,改變以後沒法再次改變狀態了。this

而後執行onFulfilledCallbacks裏面經過then註冊的回調函數。由於resolve執行的時候是異步的,因此還沒執行resolve裏面具體的代碼時候,已經經過then方法,把then中回調函數給註冊到了
onFulfilledCallbacks中,因此纔可以執行onFulfilledCallbacks裏面的回調函數。url

咱們看下then又是如何註冊回調函數的

/**
 * [註冊fulfilled狀態/rejected狀態對應的回調函數]
 * @param  {function} onFulfilled fulfilled狀態時 執行的函數
 * @param  {function} onRejected  rejected狀態時 執行的函數
 * @return {function} newPromsie  返回一個新的promise對象
 */
Promise.prototype.then = function(onFulfilled, onRejected) {
    const that = this;
    let newPromise;
    // 處理參數默認值 保證參數後續可以繼續執行
    onFulfilled =
        typeof onFulfilled === "function" ? onFulfilled : value => value;
    onRejected =
        typeof onRejected === "function" ? onRejected : reason => {
            throw reason;
        };

    // then裏面的FULFILLED/REJECTED狀態時 爲何要加setTimeout ?
    // 緣由:
    // 其一 2.2.4規範 要確保 onFulfilled 和 onRejected 方法異步執行(且應該在 then 方法被調用的那一輪事件循環以後的新執行棧中執行) 因此要在resolve里加上setTimeout
    // 其二 2.2.6規範 對於一個promise,它的then方法能夠調用屢次.(當在其餘程序中屢次調用同一個promise的then時 因爲以前狀態已經爲FULFILLED/REJECTED狀態,則會走的下面邏輯),因此要確保爲FULFILLED/REJECTED狀態後 也要異步執行onFulfilled/onRejected

    // 其二 2.2.6規範 也是resolve函數里加setTimeout的緣由
    // 總之都是 讓then方法異步執行 也就是確保onFulfilled/onRejected異步執行

    // 以下面這種情景 屢次調用p1.then
    // p1.then((value) => { // 此時p1.status 由pedding狀態 => fulfilled狀態
    //     console.log(value); // resolve
    //     // console.log(p1.status); // fulfilled
    //     p1.then(value => { // 再次p1.then 這時已經爲fulfilled狀態 走的是fulfilled狀態判斷裏的邏輯 因此咱們也要確保判斷裏面onFuilled異步執行
    //         console.log(value); // 'resolve'
    //     });
    //     console.log('當前執行棧中同步代碼');
    // })
    // console.log('全局執行棧中同步代碼');
    //

    if (that.status === FULFILLED) { // 成功態
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try{
                    let x = onFulfilled(that.value);
                    resolvePromise(newPromise, x, resolve, reject); // 新的promise resolve 上一個onFulfilled的返回值
                } catch(e) {
                    reject(e); // 捕獲前面onFulfilled中拋出的異常 then(onFulfilled, onRejected);
                }
            });
        })
    }

    if (that.status === REJECTED) { // 失敗態
        return newPromise = new Promise((resolve, reject) => {
            setTimeout(() => {
                try {
                    let x = onRejected(that.reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }

    if (that.status === PENDING) { // 等待態
        // 當異步調用resolve/rejected時 將onFulfilled/onRejected收集暫存到集合中
        return newPromise = new Promise((resolve, reject) => {
            that.onFulfilledCallbacks.push((value) => {
                try {
                    //  回調函數
                    let x = onFulfilled(value);
                    // resolve,reject都是newPromise對象下的方法
                    // x爲返回值
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
            that.onRejectedCallbacks.push((reason) => {
                try {
                    let x = onRejected(reason);
                    resolvePromise(newPromise, x, resolve, reject);
                } catch(e) {
                    reject(e);
                }
            });
        });
    }
};

正如以前所說的,先執行then方法註冊回調函數,而後執行resolve裏面代碼(pending->fulfilled),此時執行then時候that.status爲pending。

在分析that.status以前,先看下判斷onFulfilled的做用

onFulfilled =typeof onFulfilled === "function" ? onFulfilled : value => value;

若是then中並無回調函數的話,自定義個返回參數的函數。至關於下面這種

new Promise((resolve, reject) => {
    resolve('haha');
})
.then()
.then(data => console.log(data)) // haha

即便第一個then沒有回調函數,可是經過自定義的回調函數,依然把最開始的數據傳遞到了最後。

回過頭咱們看下then中that.pending 判斷語句,發現真的經過onRejectedCallbacks 數組註冊了回調函數。

總結下then的特色:

  1. 當status爲pending時候,把then中回調函數註冊到前一個Promise對象中的onFulfilledCallbacks
  2. 返回一個新的Promise實例對象

整個resolve過程以下所示:

clipboard.png

在上圖第5步時候,then中的回調函數就可使用resolve中傳入的數據了。那麼又把回調函數的
返回值放到resolvePromise裏面幹嗎,這是爲了 鏈式調用。
咱們看下resolvePromise函數

/**
 * 對resolve 進行改造加強 針對resolve中不一樣值狀況 進行處理
 * @param  {promise} promise2 promise1.then方法返回的新的promise對象
 * @param  {[type]} x         promise1中onFulfilled的返回值
 * @param  {[type]} resolve   promise2的resolve方法
 * @param  {[type]} reject    promise2的reject方法
 */
function resolvePromise(promise2, x, resolve, reject) {
    if (promise2 === x) {  // 若是從onFulfilled中返回的x 就是promise2 就會致使循環引用報錯
        return reject(new TypeError('循環引用'));
    }

    let called = false; // 避免屢次調用
    // 若是x是一個promise對象 (該判斷和下面 判斷是否是thenable對象重複 因此無關緊要)
    if (x instanceof Promise) { // 得到它的終值 繼續resolve
        if (x.status === PENDING) { // 若是爲等待態需等待直至 x 被執行或拒絕 並解析y值
            x.then(y => {
                resolvePromise(promise2, y, resolve, reject);
            }, reason => {
                reject(reason);
            });
        } else { // 若是 x 已經處於執行態/拒絕態(值已經被解析爲普通值),用相同的值執行傳遞下去 promise
            x.then(resolve, reject);
        }
        // 若是 x 爲對象或者函數
    } else if (x != null && ((typeof x === 'object') || (typeof x === 'function'))) {
        try { // 是不是thenable對象(具備then方法的對象/函數)
            let then = x.then;
            if (typeof then === 'function') {
                then.call(x, y => {
                    if(called) return;
                    called = true;
                    resolvePromise(promise2, y, resolve, reject);
                }, reason => {
                    if(called) return;
                    called = true;
                    reject(reason);
                })
            } else { // 說明是一個普通對象/函數
                resolve(x);
            }
        } catch(e) {
            if(called) return;
            called = true;
            reject(e);
        }
    } else {
        // 基本類型的值(string,number等)
        resolve(x);
    }
}

總結下要處理的返回值的類型:

1. Promise2自己 (暫時沒想通)
2. Promise對象實例
3. 含有then方法的函數或者對象
4. 普通值(string,number等)

本身觀察上面的代碼,咱們發現無論返回值是 Promise實例,仍是基本類型的值,最終都要用Promise2的resolve(返回值)來處理,而後把Promise2的resolve(返回值)中的返回值傳遞給Promise2的then中的回調函數,這樣下去就實現了鏈式操做。

若是還不懂看下面的代碼:

var obj=new Promise((resolve, reject) => {
    resolve({name:'李四'});
});
obj.then(data=>{
    data.sex='男';
    return new Promise((resolve, reject) => {
                resolve(data);
            });
}).then(data => {
   console.log(data); // {name: "李四", sex: "男"}
});

clipboard.png

總之,調用Promise2中的resolve方法,就會把數據傳遞給Promise2中的then中回調函數。

resolve中的值幾種狀況:

  • 1.普通值
  • 2.promise對象
  • 3.thenable對象/函數

若是resolve(promise對象),如何處理?

if(value instanceof Promise) {
     return value.then(resolve, reject);
}

clipboard.png

跟咱們剛纔處理的返回值是Promise實例對象同樣, 最終要把resolve裏面數據轉爲對象或者函數或者基本類型的值

注意:then中回調函數必需要有返回值
var obj=new Promise((resolve, reject) => {
    resolve({name:'張三'});
});
obj.then(data=>{
   console.log(data) // {name:'李四'}
   // 必需要有返回值
}).then(data => {
    console.log(data); // undefined
});

若是then中回調函數沒有返回值,那麼下一個then中回調函數的值不存在。

總結一下:

  • then方法把then中回調函數註冊給上一個Promise對象中的onFulfilledCallbacks數組中,並交由上一個Promise對象處理。
  • then方法返回一個新的Promise實例對象
  • resolve(data)或者resolvePromise(promise2, data, resolve, reject) 中的data值
    最終爲基本類型或者函數或者對象中的某一種