溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

這一次,徹底弄懂 Promise 原理

發(fā)布時(shí)間:2020-03-01 01:20:48 來源:網(wǎng)絡(luò) 閱讀:231 作者:wx5d61fdc401976 欄目:開發(fā)技術(shù)

Promise 必須為以下三種狀態(tài)之一:等待態(tài)(Pending)、執(zhí)行態(tài)(Fulfilled)和拒絕態(tài)(Rejected)。一旦Promise 被 resolve 或 reject,不能再遷移至其他任何狀態(tài)(即狀態(tài) immutable)。

基本過程:

初始化 Promise 狀態(tài)(pending)
執(zhí)行 then(..) 注冊(cè)回調(diào)處理數(shù)組(then 方法可被同一個(gè) promise 調(diào)用多次)
立即執(zhí)行 Promise 中傳入的 fn 函數(shù),將Promise 內(nèi)部 resolve、reject 函數(shù)作為參數(shù)傳遞給 fn ,按事件機(jī)制時(shí)機(jī)處理
Promise里的關(guān)鍵是要保證,then方法傳入的參數(shù) onFulfilled 和 onRejected,必須在then方法被調(diào)用的那一輪事件循環(huán)之后的新執(zhí)行棧中執(zhí)行。
真正的鏈?zhǔn)絇romise是指在當(dāng)前promise達(dá)到fulfilled狀態(tài)后,即開始進(jìn)行下一個(gè)promise.

鏈?zhǔn)秸{(diào)用
先從 Promise 執(zhí)行結(jié)果看一下,有如下一段代碼:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ test: 1 })
        resolve({ test: 2 })
        reject({ test: 2 })
    }, 1000)
}).then((data) => {
    console.log('result1', data)
},(data1)=>{
    console.log('result2',data1)
}).then((data) => {
    console.log('result3', data)
})
//result1 { test: 1 }
//result3 undefined

復(fù)制代碼
顯然這里輸出了不同的 data。由此可以看出幾點(diǎn):

可進(jìn)行鏈?zhǔn)秸{(diào)用,且每次 then 返回了新的 Promise(2次打印結(jié)果不一致,如果是同一個(gè)實(shí)例,打印結(jié)果應(yīng)該一致。
只輸出第一次 resolve 的內(nèi)容,reject 的內(nèi)容沒有輸出,即 Promise 是有狀態(tài)且狀態(tài)只可以由pending -> fulfilled或 pending-> rejected,是不可逆的。
then 中返回了新的 Promise,但是then中注冊(cè)的回調(diào)仍然是屬于上一個(gè) Promise 的。
基于以上幾點(diǎn),我們先寫個(gè)基于 PromiseA+ 規(guī)范的只含 resolve 方法的 Promise 模型:

function Promise(fn){ 
    let state = 'pending';
    let value = null;
    const callbacks = [];

    this.then = function (onFulfilled){
        return new Promise((resolve, reject)=>{
            handle({ //橋梁,將新 Promise 的 resolve 方法,放到前一個(gè) promise 的回調(diào)對(duì)象中
                onFulfilled, 
                resolve
            })
        })
    }

    function handle(callback){
        if(state === 'pending'){
            callbacks.push(callback)
            return;
        }

        if(state === 'fulfilled'){
            if(!callback.onFulfilled){
                callback.resolve(value)
                return;
            }
            const ret = callback.onFulfilled(value) //處理回調(diào)
            callback.resolve(ret) //處理下一個(gè) promise 的resolve
        }
    }
    function resolve(newValue){
        const fn = ()=>{
            if(state !== 'pending')return

            state = 'fulfilled';
            value = newValue
            handelCb()
        }

        setTimeout(fn,0) //基于 PromiseA+ 規(guī)范
    }

    function handelCb(){
        while(callbacks.length) {
            const fulfiledFn = callbacks.shift();
            handle(fulfiledFn);
        };
    }

    fn(resolve)
}

復(fù)制代碼
這個(gè)模型簡(jiǎn)單易懂,這里最關(guān)鍵的點(diǎn)就是在 then 中新創(chuàng)建的 Promise,它的狀態(tài)變?yōu)?fulfilled 的節(jié)點(diǎn)是在上一個(gè) Promise的回調(diào)執(zhí)行完畢的時(shí)候。也就是說當(dāng)一個(gè) Promise 的狀態(tài)被 fulfilled 之后,會(huì)執(zhí)行其回調(diào)函數(shù),而回調(diào)函數(shù)返回的結(jié)果會(huì)被當(dāng)作 value,返回給下一個(gè) Promise(也就是then 中產(chǎn)生的 Promise),同時(shí)下一個(gè) Promise的狀態(tài)也會(huì)被改變(執(zhí)行 resolve 或 reject),然后再去執(zhí)行其回調(diào),以此類推下去...鏈?zhǔn)秸{(diào)用的效應(yīng)就出來了。

但是如果僅僅是例子中的情況,我們可以這樣寫:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ test: 1 })
    }, 1000)
}).then((data) => {
    console.log('result1', data)
    //dosomething
    console.log('result3')
})
//result1 { test: 1 }
//result3

復(fù)制代碼
實(shí)際上,我們常用的鏈?zhǔn)秸{(diào)用,是用在異步回調(diào)中,以解決"回調(diào)地獄"的問題。如下例子:

new Promise((resolve, reject) => {
setTimeout(() => {
resolve({ test: 1 })
}, 1000)
}).then((data) => {
console.log('result1', data)
//dosomething
return test()
}).then((data) => {
console.log('result2', data)
})

function test(id) {
return new Promise(((resolve) => {
setTimeout(() => {
resolve({ test: 2 })
}, 5000)
}))
}
//基于第一個(gè) Promise 模型,執(zhí)行后的輸出
//result1 { test: 1 }
//result2 Promise {then: ?}
復(fù)制代碼
用上面的 Promise 模型,得到的結(jié)果顯然不是我們想要的。認(rèn)真看上面的模型,執(zhí)行 callback.resolve 時(shí),傳入的參數(shù)是 callback.onFulfilled 執(zhí)行完成的返回,顯然這個(gè)測(cè)試?yán)臃祷氐木褪且粋€(gè) Promise,而我們的 Promise 模型中的 resolve 方法并沒有特殊處理。那么我們將 resolve 改一下:

function Promise(fn){ 
    ...
    function resolve(newValue){
        const fn = ()=>{
            if(state !== 'pending')return

            if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
                const {then} = newValue
                if(typeof then === 'function'){
                    // newValue 為新產(chǎn)生的 Promise,此時(shí)resolve為上個(gè) promise 的resolve
                    //相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個(gè) promise 的resolve 為其回調(diào)
                    then.call(newValue,resolve)
                    return
                }
            }
            state = 'fulfilled';
            value = newValue
            handelCb()
        }

        setTimeout(fn,0)
    }
    ...
}

復(fù)制代碼
用這個(gè)模型,再測(cè)試我們的例子,就得到了正確的結(jié)果:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ test: 1 })
    }, 1000)
}).then((data) => {
    console.log('result1', data)
    //dosomething
    return test()
}).then((data) => {
    console.log('result2', data)
})

function test(id) {
    return new Promise(((resolve, reject) => {
        setTimeout(() => {
        resolve({ test: 2 })
        }, 5000)
    }))
}
//result1 { test: 1 }
//result2 { test: 2 }

復(fù)制代碼
顯然,新增的邏輯就是針對(duì) resolve 入?yún)?Promise 的時(shí)候的處理。我們觀察一下 test 里面創(chuàng)建的 Promise,它是沒有調(diào)用 then方法的。從上面的分析我們已經(jīng)知道 Promise 的回調(diào)函數(shù)就是通過調(diào)用其 then 方法注冊(cè)的,因此 test 里面創(chuàng)建的 Promise 其回調(diào)函數(shù)為空。

顯然如果沒有回調(diào)函數(shù),執(zhí)行 resolve 的時(shí)候,是沒辦法鏈?zhǔn)较氯サ摹R虼?,我們需要主?dòng)為其注入回調(diào)函數(shù)。

我們只要把第一個(gè) then 中產(chǎn)生的 Promise 的 resolve 函數(shù)的執(zhí)行,延遲到 test 里面的 Promise 的狀態(tài)為 onFulfilled 的時(shí)候再執(zhí)行,那么鏈?zhǔn)骄涂梢岳^續(xù)了。所以,當(dāng) resolve 入?yún)?Promise 的時(shí)候,調(diào)用其 then 方法為其注入回調(diào)函數(shù),而注入的是前一個(gè) Promise 的 resolve 方法,所以要用 call 來綁定 this 的指向。

基于新的 Promise 模型,上面的執(zhí)行過程產(chǎn)生的 Promise 實(shí)例及其回調(diào)函數(shù),可以用看下表:

Promise callback
P1 [{onFulfilled:c1(第一個(gè)then中的fn),resolve:p2resolve}]
P2 (P1 調(diào)用 then 時(shí)產(chǎn)生) [{onFulfilled:c2(第二個(gè)then中的fn),resolve:p3resolve}]
P3 (P2 調(diào)用 then 時(shí)產(chǎn)生) []
P4 (執(zhí)行c1中產(chǎn)生[調(diào)用 test ]) [{onFulfilled:p2resolve,resolve:p5resolve}]
P5 (調(diào)用p2resolve 時(shí),進(jìn)入 then.call 邏輯中產(chǎn)生) []
有了這個(gè)表格,我們就可以清晰知道各個(gè)實(shí)例中 callback 執(zhí)行的順序是:

c1 -> p2resolve -> c2 -> p3resolve -> [] -> p5resolve -> []

以上就是鏈?zhǔn)秸{(diào)用的原理了。

reject
下面我們?cè)賮硌a(bǔ)全 reject 的邏輯。只需要在注冊(cè)回調(diào)、狀態(tài)改變時(shí)加上 reject 的邏輯即可。

完整代碼如下:

function Promise(fn){ 
    let state = 'pending';
    let value = null;
    const callbacks = [];

    this.then = function (onFulfilled,onRejected){
        return new Promise((resolve, reject)=>{
            handle({
                onFulfilled, 
                onRejected,
                resolve, 
                reject
            })
        })
    }

    function handle(callback){
        if(state === 'pending'){
            callbacks.push(callback)
            return;
        }

        const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
        const next = state === 'fulfilled'? callback.resolve:callback.reject;

        if(!cb){
            next(value)
            return;
        }
        const ret = cb(value)
        next(ret)
    }
    function resolve(newValue){
        const fn = ()=>{
            if(state !== 'pending')return

            if(newValue && (typeof newValue === 'object' || typeof newValue === 'function')){
                const {then} = newValue
                if(typeof then === 'function'){
                    // newValue 為新產(chǎn)生的 Promise,此時(shí)resolve為上個(gè) promise 的resolve
                    //相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個(gè) promise 的resolve 為其回調(diào)
                    then.call(newValue,resolve, reject)
                    return
                }
            }
            state = 'fulfilled';
            value = newValue
            handelCb()
        }

        setTimeout(fn,0)
    }
    function reject(error){

        const fn = ()=>{
            if(state !== 'pending')return

            if(error && (typeof error === 'object' || typeof error === 'function')){
                const {then} = error
                if(typeof then === 'function'){
                    then.call(error,resolve, reject)
                    return
                }
            }
            state = 'rejected';
            value = error
            handelCb()
        }
        setTimeout(fn,0)
    }
    function handelCb(){
        while(callbacks.length) {
            const fn = callbacks.shift();
            handle(fn);
        };
    }
    fn(resolve, reject)
}

復(fù)制代碼
異常處理
異常通常是指在執(zhí)行成功/失敗回調(diào)時(shí)代碼出錯(cuò)產(chǎn)生的錯(cuò)誤,對(duì)于這類異常,我們使用 try-catch 來捕獲錯(cuò)誤,并將 Promise 設(shè)為 rejected 狀態(tài)即可。

handle代碼改造如下:

function handle(callback){
    if(state === 'pending'){
        callbacks.push(callback)
        return;
    }

    const cb = state === 'fulfilled' ? callback.onFulfilled:callback.onRejected;
    const next = state === 'fulfilled'? callback.resolve:callback.reject;

    if(!cb){
        next(value)
        return;
    }
    try {
        const ret = cb(value)
        next(ret)
    } catch (e) {
        callback.reject(e);
    }  
}

復(fù)制代碼
我們實(shí)際使用時(shí),常習(xí)慣注冊(cè) catch 方法來處理錯(cuò)誤,例:

new Promise((resolve, reject) => {
    setTimeout(() => {
        resolve({ test: 1 })
    }, 1000)
}).then((data) => {
    console.log('result1', data)
    //dosomething
    return test()
}).catch((ex) => {
    console.log('error', ex)
})

復(fù)制代碼
實(shí)際上,錯(cuò)誤也好,異常也罷,最終都是通過reject實(shí)現(xiàn)的。也就是說可以通過 then 中的錯(cuò)誤回調(diào)來處理。所以我們可以增加這樣的一個(gè) catch 方法:

function Promise(fn){ 
    ...
    this.then = function (onFulfilled,onRejected){
        return new Promise((resolve, reject)=>{
            handle({
                onFulfilled, 
                onRejected,
                resolve, 
                reject
            })
        })
    }
    this.catch = function (onError){
        this.then(null,onError)
    }
    ...
}

復(fù)制代碼
Finally方法
在實(shí)際應(yīng)用的時(shí)候,我們很容易會(huì)碰到這樣的場(chǎng)景,不管Promise最后的狀態(tài)如何,都要執(zhí)行一些最后的操作。我們把這些操作放到 finally 中,也就是說 finally 注冊(cè)的函數(shù)是與 Promise 的狀態(tài)無關(guān)的,不依賴 Promise 的執(zhí)行結(jié)果。所以我們可以這樣寫 finally 的邏輯:

function Promise(fn){ 
    ...
    this.catch = function (onError){
        this.then(null,onError)
    }
    this.finally = function (onDone){
        this.then(onDone,onDone)
    }
    ...
}

復(fù)制代碼
resolve 方法和 reject 方法
實(shí)際應(yīng)用中,我們可以使用 Promise.resolve 和 Promise.reject 方法,用于將于將非 Promise 實(shí)例包裝為 Promise 實(shí)例。如下例子:

Promise.resolve({name:'winty'})
Promise.reject({name:'winty'})
// 等價(jià)于
new Promise(resolve => resolve({name:'winty'}))
new Promise((resolve,reject) => reject({name:'winty'}))
復(fù)制代碼
這些情況下,Promise.resolve 的入?yún)⒖赡苡幸韵聨追N情況:

無參數(shù) [直接返回一個(gè)resolved狀態(tài)的 Promise 對(duì)象]
普通數(shù)據(jù)對(duì)象 [直接返回一個(gè)resolved狀態(tài)的 Promise 對(duì)象]
一個(gè)Promise實(shí)例 [直接返回當(dāng)前實(shí)例]
一個(gè)thenable對(duì)象(thenable對(duì)象指的是具有then方法的對(duì)象) [轉(zhuǎn)為 Promise 對(duì)象,并立即執(zhí)行thenable對(duì)象的then方法。]
基于以上幾點(diǎn),我們可以實(shí)現(xiàn)一個(gè) Promise.resolve 方法如下:

function Promise(fn){ 
    ...
    this.resolve = function (value){
        if (value && value instanceof Promise) {
            return value;
        } else if (value && typeof value === 'object' && typeof value.then === 'function'){
            let then = value.then;
            return new Promise(resolve => {
                then(resolve);
            });
        } else if (value) {
            return new Promise(resolve => resolve(value));
        } else {
            return new Promise(resolve => resolve());
        }
    }
    ...
}

復(fù)制代碼
Promise.reject與Promise.resolve類似,區(qū)別在于Promise.reject始終返回一個(gè)狀態(tài)的rejected的Promise實(shí)例,而Promise.resolve的參數(shù)如果是一個(gè)Promise實(shí)例的話,返回的是參數(shù)對(duì)應(yīng)的Promise實(shí)例,所以狀態(tài)不一 定。 因此,reject 的實(shí)現(xiàn)就簡(jiǎn)單多了,如下:

function Promise(fn){ 
    ...
    this.reject = function (value){
        return new Promise(function(resolve, reject) {
            reject(value);
        });
    }
    ...
}

復(fù)制代碼
Promise.all
入?yún)⑹且粋€(gè) Promise 的實(shí)例數(shù)組,然后注冊(cè)一個(gè) then 方法,然后是數(shù)組中的 Promise 實(shí)例的狀態(tài)都轉(zhuǎn)為 fulfilled 之后則執(zhí)行 then 方法。這里主要就是一個(gè)計(jì)數(shù)邏輯,每當(dāng)一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 之后就保存該實(shí)例返回的數(shù)據(jù),然后將計(jì)數(shù)減一,當(dāng)計(jì)數(shù)器變?yōu)?0 時(shí),代表數(shù)組中所有 Promise 實(shí)例都執(zhí)行完畢。

function Promise(fn){ 
    ...
    this.all = function (arr){
        var args = Array.prototype.slice.call(arr);
        return new Promise(function(resolve, reject) {
            if(args.length === 0) return resolve([]);
            var remaining = args.length;

            function res(i, val) {
                try {
                    if(val && (typeof val === 'object' || typeof val === 'function')) {
                        var then = val.then;
                        if(typeof then === 'function') {
                            then.call(val, function(val) {
                                res(i, val);
                            }, reject);
                            return;
                        }
                    }
                    args[i] = val;
                    if(--remaining === 0) {
                        resolve(args);
                    }
                } catch(ex) {
                    reject(ex);
                }
            }
            for(var i = 0; i < args.length; i++) {
                res(i, args[i]);
            }
        });
    }
    ...
}

復(fù)制代碼
Promise.race
有了 Promise.all 的理解,Promise.race 理解起來就更容易了。它的入?yún)⒁彩且粋€(gè) Promise 實(shí)例數(shù)組,然后其 then 注冊(cè)的回調(diào)方法是數(shù)組中的某一個(gè) Promise 的狀態(tài)變?yōu)?fulfilled 的時(shí)候就執(zhí)行。因?yàn)?Promise 的狀態(tài)只能改變一次,那么我們只需要把 Promise.race 中產(chǎn)生的 Promise 對(duì)象的 resolve 方法,注入到數(shù)組中的每一個(gè) Promise 實(shí)例中的回調(diào)函數(shù)中即可。

function Promise(fn){
...
this.race = function(values) {
return new Promise(function(resolve, reject) {
for(var i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject);
}
});
}
...
}
復(fù)制代碼
總結(jié)
Promise 源碼不過幾百行,我們可以從執(zhí)行結(jié)果出發(fā),分析每一步的執(zhí)行過程,然后思考其作用即可。其中最關(guān)鍵的點(diǎn)就是要理解 then 函數(shù)是負(fù)責(zé)注冊(cè)回調(diào)的,真正的執(zhí)行是在 Promise 的狀態(tài)被改變之后。而當(dāng) resolve 的入?yún)⑹且粋€(gè) Promise 時(shí),要想鏈?zhǔn)秸{(diào)用起來,就必須調(diào)用其 then 方法(then.call),將上一個(gè) Promise 的 resolve 方法注入其回調(diào)數(shù)組中。

補(bǔ)充說明
雖然 then 普遍認(rèn)為是微任務(wù)。但是瀏覽器沒辦法模擬微任務(wù),目前要么用 setImmediate ,這個(gè)也是宏任務(wù),且不兼容的情況下還是用 setTimeout 打底的。還有,promise 的 polyfill (es6-promise) 里用的也是 setTimeout。因此這里就直接用 setTimeout,以宏任務(wù)來代替微任務(wù)了。

參考資料
PromiseA+規(guī)范
Promise 實(shí)現(xiàn)原理精解
30分鐘,讓你徹底明白Promise原理
完整 Promise 模型
function Promise(fn) {
let state = 'pending'
let value = null
const callbacks = []

this.then = function (onFulfilled, onRejected) {
return new Promise((resolve, reject) => {
handle({
onFulfilled,
onRejected,
resolve,
reject,
})
})
}

this.catch = function (onError) {
this.then(null, onError)
}

this.finally = function (onDone) {
this.then(onDone, onError)
}

this.resolve = function (value) {
if (value && value instanceof Promise) {
return value
} if (value && typeof value === 'object' && typeof value.then === 'function') {
const { then } = value
return new Promise((resolve) => {
then(resolve)
})
} if (value) {
return new Promise(resolve => resolve(value))
}
return new Promise(resolve => resolve())
}

this.reject = function (value) {
return new Promise(((resolve, reject) => {
reject(value)
}))
}

this.all = function (arr) {
const args = Array.prototype.slice.call(arr)
return new Promise(((resolve, reject) => {
if (args.length === 0) return resolve([])
let remaining = args.length

  function res(i, val) {
    try {
      if (val && (typeof val === 'object' || typeof val === 'function')) {
        const { then } = val
        if (typeof then === 'function') {
          then.call(val, (val) => {
            res(i, val)
          }, reject)
          return
        }
      }
      args[i] = val
      if (--remaining === 0) {
        resolve(args)
      }
    } catch (ex) {
      reject(ex)
    }
  }
  for (let i = 0; i < args.length; i++) {
    res(i, args[i])
  }
}))

}

this.race = function (values) {
return new Promise(((resolve, reject) => {
for (let i = 0, len = values.length; i < len; i++) {
values[i].then(resolve, reject)
}
}))
}

function handle(callback) {
if (state === 'pending') {
callbacks.push(callback)
return
}

const cb = state === 'fulfilled' ? callback.onFulfilled : callback.onRejected
const next = state === 'fulfilled' ? callback.resolve : callback.reject

if (!cb) {
  next(value)
  return
}
try {
  const ret = cb(value)
  next(ret)
} catch (e) {
  callback.reject(e)
}

}
function resolve(newValue) {
const fn = () => {
if (state !== 'pending') return

  if (newValue && (typeof newValue === 'object' || typeof newValue === 'function')) {
    const { then } = newValue
    if (typeof then === 'function') {
      // newValue 為新產(chǎn)生的 Promise,此時(shí)resolve為上個(gè) promise 的resolve
      // 相當(dāng)于調(diào)用了新產(chǎn)生 Promise 的then方法,注入了上個(gè) promise 的resolve 為其回調(diào)
      then.call(newValue, resolve, reject)
      return
    }
  }
  state = 'fulfilled'
  value = newValue
  handelCb()
}

setTimeout(fn, 0)

}
function reject(error) {
const fn = () => {
if (state !== 'pending') return

  if (error && (typeof error === 'object' || typeof error === 'function')) {
    const { then } = error
    if (typeof then === 'function') {
      then.call(error, resolve, reject)
      return
    }
  }
  state = 'rejected'
  value = error
  handelCb()
}
setTimeout(fn, 0)

}
function handelCb() {
while (callbacks.length) {
const fn = callbacks.shift()
handle(fn)
}
}
fn(resolve, reject)
}

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI