Promise 的then 裏發生了什麼

Promise 大家都知道怎麼用, 但是對於內部的原理很多人都不是很清楚

來看一個面試題: Promise的then 是怎麼實現的

首先來分析一下then

  • then是屬於實例上的方法
  • 參數有2個,分別爲onFulfilled, onRejected,並且都是可選的
  • 可以實現鏈式調用
  • then執行要執行Promise onFulfilled 或者 onRejected 方法
  • 參數onFulfilled,onRejected 分別有自己的參數, 分別是resolve的參數跟reject的參數
  • then只能使用前一個then的返回值
  • then返回值不能是同一個promise

來一個一個看吧

  • then是屬於實例上的方法
Promise.prototype.then = function(){}
  • 參數有2個,分別爲onFulfilled, onRejected,並且都是可選的
Promise.prototype.then = function(onFulfilled,onRejected){}
  • 可以實現鏈式調用
Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise(function(resolve,reject){
        // 代碼省略
    })
}

要實現promise鏈式 返回的必須是一個Promise 再由於status 改變狀態了不能再變 所以需要第二個.... 第N個promise 調用新的resolve

  • then執行要執行Promise onFulfilled 或者 onRejected 方法
Promise.prototype.then = function(onFulfilled,onRejected){
    var self = this; // 保存當前上下文
    return new Promise(function(resolve,reject){
        if(self.status === 'resolved'){
             onFulfilled(self.res)
        }
        if(self.status === 'rejected'){
            onRejected(self.err)
        }
    })
}

Promise.resolve(res) 、、 同步代碼的執行情況下 上述邏輯成立

  • 參數onFulfilled,onRejected 分別有自己的參數, 分別是resolve的參數跟reject的參數

res 跟err 來源如下

function Promise(executor){
    let self = this
    this.status = 'pending'  // Promise 的狀態值
    this.res = undefined   // 存成功之後的值
    this.err = undefined   // 存失敗之後的值
    executor(resolve,reject)
    // 省略代碼
}

executor 有2個參數 分別爲resolve,reject
當調用

new Promise((resolve,reject)=>{
    setTimeout(()=>{
        if(true){
            resolve('res')
        }else{
            reject('err')
        }
       
    },1000)
})

executor 會執行 並且把成功的值或者失敗的值拋出來,resolve跟reject也是2個函數 定義在Promise內部

function Promise(executor){
     // ...代碼省略
    function resolve(res){
        self.res = res 
    }
    function reject(err){
        self.err = err 
    }
    executor(resolve,reject)
    
}

resolve 跟reject 還需要改變 Promise的狀態值 並且一旦發生改變之後不能再次改變

function Promise(executor){
    // 代碼省略
    function resolve(res){
        if(self.status === 'pending'){
            self.status = 'resolved'
            self.res = res 
        }
    }
    function reject(err){
        if(self.status === 'pending'){
            self.status = 'rejected'
            self.err = err 
        }
    }
    executor(resolve,reject)
}

我們在executor中操作的往往是異步代碼, 這個之後直接then, status的狀態值並未發生改變, 所以不會執行onFulfilled跟onRejected,
這個時候我們需要訂閱

function Promise(executor){
    // 代碼省略
    this.onFulfilledCallback = []  // 存成功之後的回調
    this.onrejectedCallback = []  // 存失敗之後的回調
    function resolve(res){
        if(self.status === 'pending'){
            self.status = 'resolved'
            self.res = res 
        }
    }
    function reject(err){
        if(self.status === 'pending'){
            self.status = 'rejected'
            self.err = err 
        }
    }
    executor(resolve,reject)
}

new Promise(executor).then((onFulfilled,onrejected)=>{
    var self = this; // 保存當前上下文   **注意: 第二次調用then this是指向new Promise的**
    return new Promise(function(resolve,reject){
        // ...代碼省略
        if(self.status === 'pending'){
            self.onFulfilledCallback.push(function(){   // 把成功之後需要做的事存起來  有多少個then就有多少個函數
                onFulfilled(self.res)   // 注意 這裏的self.res 會隨着then的調用發生改變  因爲每次then都new 了一個Promise
            })
            self.onrejectedCallback.push(function(){   // 把失敗之後的事存起來 有多少個then就有多少個函數
                onFulfilled(self.err)
            })
        }
    })
})

當resolve的時候 或者reject的時候
一一拿出來執行

function resolve(res){
    // 省略代碼
    self.res = res
    onFulfilledCallback.forEach(fn=>{
        fn()
    })
}

實現鏈式調用我們需要對then函數執行的返回值做一個操作!!!

需求:

  1. then的參數onFulfilled函數 需要接收到上一個onfulfilled的執行結果
Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise((resolve,reject)=>{
        // ...代碼省略
        if(self.status === 'resolved'){
            let x = onFulfilled(self.res)   // 拿到onFulfilled的執行結果  注意:這裏執行Promise.resolve() 同步代碼
            // 然後把x傳遞給下一個then
            resolve(x)
        }
    })
}

如果resolve是異步的話

Promise.prototype.then = function(onFulfilled,onRejected){
    return new Promise((resolve,reject)=>{
        // ...代碼省略
        if(self.status === 'resolved'){
            let x = onFulfilled(self.res)   // 拿到onFulfilled的執行結果  注意:這裏執行的是Promise.resolve() 同步代碼
            // 然後把x傳遞給下一個then
            resolve(x)
        }
        if(self.status === 'pending'){
            self.onFulfilledCallback.push(function(){
                let x = onFulfilled(self.res)  // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 相當於 fn(){let x = onFulfilled}  
                resolve(x)
            })
        }
    })
}

//  同時爲了拿到 fn(){let x = onFulfilled ...} 得值 傳遞給下一個onFulfilled,onFulfilledCallback需要改寫 onRejectedCallback同理

onFulfilledCallback.forEach(fn=>{
    return fn()
})
onRejectedCallback.forEach(fn=>{
    return fn()
})

-

  • onFullfillled 的返回值可能是個promise 也可能是個普通值

考慮如下情況:

1. 返回值是個promise

Promsie.resolve(11).then(res=>{

    return new Promise(executor) 
})

2. 返回值是個普通值

Promsie.resolve(11).then(res=>{

    return 1
})

最關鍵的來了
我們需要對onFullfillled 的返回值進行判斷

修改then函數

// ...代碼省略
if(self.status === 'resolved'){
        let x = onFulfilled(self.res)   // 拿到onFulfilled的執行結果  注意:這裏執行的是Promise.resolve() 同步代碼
        // 然後把x傳遞給下一個then
        promiseResolve(x,resolve,reject)
        
    }
function promiseResolve(x,resolve,reject){
    if((typeof x != null && typeof x == 'object') || typeof x == 'function'){  // 確定是個引用類型
        if (x instanceof Promise){  // 確定是個Promise 實例
            let then = x.then
            then.call(x,res=>{    //  保證x調用res=>{}, res=>{}是then的onFulfilled方法  一直判斷直到onFulfilled 返回的是個普通值 然後resolve出去
                promiseResolve(res,resolve,reject)
            },err=>{
                reject(err)
            })
        }
    }else{
        resolve(x)
    }
}
  • then返回值不能是同一個promise

考慮這樣極端問題:

let p = new Promise(resolve=>{
    resolve(3333)
})

let p2 = p.then(resolve=>{
    return p2
})

這樣會出現什麼問題呢, onFulfilled的返回結果如果是一個promise 上面的遞歸調用已經說明了 我們要不斷的去遞歸最終的onFulfilled結果 然後再改變p2的status 這樣變成了p2去等待p2的執行結果 函數死掉了

所以onFulfilled的返回結果需要跟then的返回結果去比較 修改函數

function promiseResolve(x,resolve,reject,promise){
    if(x === promise ){
        return new Error('引用錯誤!')
    }
    if((typeof x != null && typeof x == 'object') || typeof x == 'function'){  // 確定是個引用類型
        if (x instanceof Promise){  // 確定是個Promise 實例
            let then = x.then
            then.call(x,res=>{    //  保證x調用res=>{}, res=>{}是then的onFulfilled方法  一直判斷直到onFulfilled 返回的是個普通值 然後resolve出去
                promiseResolve(res,resolve,reject)
            },err=>{
                reject(err)
            })
        }
    }else{
        resolve(x)
    }
}

Promise.prototype.then = function(onFulfilled,onRejected){

    // ...代碼省略
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === 'pending'){
            self.onFulfilledCallback.push(function(){
                let x = onFulfilled(self.res)  // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 相當於 fn(){let x = onFulfilled}  
                promiseResolve(x,resolve,reject,promise2)
            })
        }
    })
    return promise2
}

這段代碼還是有錯誤, 由於javascript主線程是單線程的, 所以在then裏的promiseResolve是拿不到promise2的 所以我們需要開啓異步 使用定時器或者nextTick 這裏我們用定時器

Promise.prototype.then = function(onFulfilled,onRejected){

    // ...代碼省略
    let promise2 = new Promise((resolve,reject)=>{
        if(self.status === 'pending'){
            self.onFulfilledCallback.push(function(){
                setTimeout(()=>{
                    let x = onFulfilled(self.res)  // 這裏的self.res 是上一個new Promise上的值 此時的onFUlfilled 相當於 fn(){let x = onFulfilled}  
                    promiseResolve(x,resolve,reject,promise2)
                },0)
            })
        }
    })
    return promise2
}

此時 整個then差不多完成了

需要補充的點

  • onFulfilled,onRejected 有可能是undefined 這裏未做判斷
  • 在new Promise 時 executor函數內部有可能報錯 這裏未使用try catch捕獲
  • 這裏對於onRejected的錯誤未拋出

總結

then每次創建一個新的promise對象 對於同步的resolve,reject直接調用onFulfilled或者onRejected ,對於異步的resolve,reject使用
訂閱發佈模式,把每個resolve,reject 暫存起來 等達到條件時候一一執行, 並且拿到返回結果去跟內部的promise比較,並且判斷如果是一個promise的話,不斷解析onFulfilled 的返回結果 直至resolve出去

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