Promise的特性及實現(xiàn)原理

Promise對象的特性

要實現(xiàn)Promise對象首先我們要了解Promise擁有哪些特性率挣,簡單概括為以下幾點
1绑榴、Promise有三種狀態(tài):pending(進行中)朵逝、fulfilled(已成功)淳蔼、rejected(已失敗)
2、Promise對象接受一個回調(diào)函數(shù)作為參數(shù), 該回調(diào)函數(shù)接受兩個參數(shù)撤嫩,分別是成功時的回調(diào)resolve和失敗時的回調(diào)reject偎捎;另外resolve的參數(shù)除了正常值以外, 還可能是一個Promise對象的實例序攘;reject的參數(shù)通常是一個Error對象的實例茴她。
3、then方法返回一個新的Promise實例程奠,并接收兩個參數(shù)onResolved(fulfilled狀態(tài)的回調(diào))丈牢;
onRejected(rejected狀態(tài)的回調(diào),該參數(shù)可選)
4梦染、catch方法返回一個新的Promise實例
5赡麦、finally方法不管Promise狀態(tài)如何都會執(zhí)行,該方法的回調(diào)函數(shù)不接受任何參數(shù)
6帕识、Promise.all()方法將多個多個Promise實例泛粹,包裝成一個新的Promise實例,該方法接受一個由Promise對象
組成的數(shù)組作為參數(shù)(Promise.all()方法的參數(shù)可以不是數(shù)組肮疗,但必須具有Iterator接口晶姊,且返回的每個成員都是Promise實例),注意參數(shù)中只要有一個實例觸發(fā)catch方法伪货,都會觸發(fā)Promise.all()方法返回的新的實例的catch方法们衙,如果參數(shù)中的某個實例本身調(diào)用了catch方法钾怔,將不會觸發(fā)Promise.all()方法返回的新實例的catch方法
7、Promise.race()方法的參數(shù)與Promise.all方法一樣蒙挑,參數(shù)中的實例只要有一個率先改變狀態(tài)就會將該實例的狀態(tài)傳給Promise.race()方法宗侦,并將返回值作為Promise.race()方法產(chǎn)生的Promise實例的返回值
8、Promise.resolve()將現(xiàn)有對象轉(zhuǎn)為Promise對象忆蚀,如果該方法的參數(shù)為一個Promise對象矾利,Promise.resolve()將不做任何處理;如果參數(shù)thenable對象(即具有then方法)馋袜,Promise.resolve()將該對象轉(zhuǎn)為Promise對象并立即執(zhí)行then方法男旗;如果參數(shù)是一個原始值,或者是一個不具有then方法的對象欣鳖,則Promise.resolve方法返回一個新的Promise對象察皇,狀態(tài)為fulfilled,其參數(shù)將會作為then方法中onResolved回調(diào)函數(shù)的參數(shù)泽台,如果Promise.resolve方法不帶參數(shù)什荣,會直接返回一個fulfilled狀態(tài)的 Promise 對象。需要注意的是师痕,立即resolve()的 Promise 對象溃睹,是在本輪“事件循環(huán)”(event loop)的結(jié)束時執(zhí)行而账,而不是在下一輪“事件循環(huán)”的開始時胰坟。

setTimeout(function () {
  console.log('three');
}, 0);

Promise.resolve().then(function () {
  console.log('two');
});

console.log('one');

// one
// two
// three

9、Promise.reject()同樣返回一個新的Promise對象泞辐,狀態(tài)為rejected笔横,無論傳入任何參數(shù)都將作為reject()的參數(shù)
以上就是Promise對象的一些基本特性,下面我們根據(jù)Promise對象的特性咐吼,自己來實現(xiàn)一個簡單的Promise對象

第一步吹缔、Promise對象用三種狀態(tài)分別是:pending、fulfilled锯茄、rejected厢塘。
resolve的參數(shù)為一個Promise對象的實例的時時候該實例的resolve的參數(shù)即為外層Promise對象then方法中onResolved方法的參數(shù),reject的參數(shù)即為外層Promise對象then方法(或catch方法)中onRejected方法的參數(shù)

function timeout2() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                a // 此處a未定義肌幽,如果注釋掉這里即正常執(zhí)行
                resolve(200)
            } catch (e) {
                reject(e)
            }
        }, 1000)
    })
}
function timeout(time) {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            try {
                resolve(timeout2())
            } catch (e) {
                reject(e)
            }
        }, time)
    })
}
let p = timeout(1000);
p.then(res => {
    console.log(res); // 200
}).catch(err => {
    console.log(err); // ReferenceError: a is not defined
})

由上面的例子我們可以定義一個基本框架:

/*
    Promise構(gòu)造函數(shù)接收一個executor函數(shù)晚碾, executor函數(shù)執(zhí)行完同步或異步操作后,調(diào)用它的兩個參數(shù)resolve和reject
    如果操作成功喂急,調(diào)用resolve并傳入value
    如果操作失敗格嘁,調(diào)用reject并傳入reason
*/
class MyPromise {
    constructor(executor) {
        if(typeof executor !== 'function') {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        // Promise當前的狀態(tài)
        this.status = 'pending'
        // Promise的值
        this.data = undefined
        // Promise resolve時的回調(diào)函數(shù)集,因為在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
        this.onResolvedCallback = []
        // Promise reject時的回調(diào)函數(shù)集廊移,因為在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
        this.onRejectedCallback = []
        /*
            考慮到執(zhí)行executor的過程中有可能出錯糕簿,所以我們用try/catch塊給包起來探入,
            并且在出錯后以catch到的值reject掉這個Promise,另外因為resolve和reject在外部調(diào)用故需要綁定this
        */
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (err) {
            this.reject(err)
        }
    }

    resolve(value) {
        // 成功時將狀態(tài)改為fulfilled
        if(this.status === 'padding') {
            // 如果傳入的值是一個promise實例,則必須等待該Promise對象狀態(tài)改變后,
            // 當前Promsie的狀態(tài)才會改變懂诗,且狀態(tài)取決于參數(shù)Promsie對象的狀態(tài)
            if(value instanceof MyPromise) {
                value.then(res => {
                    this.data = res
                    this.status = 'fulfilled'
                    //執(zhí)行resolve的回調(diào)函數(shù)蜂嗽,將value傳遞到callback中
                    this.onResolvedCallback.forEach(callback => callback(res))
                }, err => {
                    this.data = err
                    this.status = 'rejected'
                    //執(zhí)行reject的回調(diào)函數(shù),將reason傳遞到callback中
                    this.onRejectedCallback.forEach(callback => callback(err))
                })
            } else {
                this.status = 'fulfilled';
                this.data = value;
                //執(zhí)行resolve的回調(diào)函數(shù)殃恒,將value傳遞到callback中
                this.onResolvedCallback.forEach(callback => callback(value))
            }
        }
    }
    reject(reason) {
        // 失敗時將狀態(tài)改為rejected
        if(this.status === 'padding') {
            this.status = 'rejected'
            this.data = reason;
            // 觸發(fā)所有的回調(diào)函數(shù)
            this.onRejectedCallback.forEach(item => {
                item(reason)
            })
        }
    }

}

第二步徒爹、實現(xiàn)then方法
Promise實例的then方法返回一個新的Promise實例,并接收兩個參數(shù)onResolved(fulfilled狀態(tài)的回調(diào))芋类; onRejected(rejected狀態(tài)的回調(diào)隆嗅,該參數(shù)可選);

// then方法接收兩個參數(shù),onResolved侯繁,onRejected胖喳,分別為Promise成功或失敗后的回調(diào)
class MyPromise {
    // ....
    then(onResolved, onRejected) {
        // 根據(jù)標準,如果then的參數(shù)不是function贮竟,則我們需要忽略它丽焊,此處以如下方式處理
        onResolved = typeof onResolved === 'function' ? onResolved : value => {}
        onRejected = typeof onRejected === 'function' ? onRejected : reason => {}

        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {

            })
        }
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {

            })
        }
        if (this.status === 'pending') {
            return new MyPromise((resolve, reject) => {

            })
        }
    }
}

當我們在鏈式調(diào)用Promise實例的時候,當一個實例的then方法返回另一個實例咕别,第二個then方法指定的回調(diào)函數(shù)技健,就會等待這個新的Promise對象狀態(tài)發(fā)生變化。如果變?yōu)閒ulfilled惰拱,就調(diào)用第一個回調(diào)函數(shù)(即onResolved)雌贱,如果狀態(tài)變?yōu)閞ejected,就調(diào)用第二個回調(diào)函數(shù)(即onRejected)偿短。

let p1 = new Promise((resolve,reject) => {
    setTimeout(function(){
        try {
            resolve(1)
        } catch (e) {
            reject(e)
        }
    }, 100)
})
let p2 = new Promise((resolve,reject) => {
    setTimeout(function(){
        try {
            resolve(2)
        } catch (e) {
            reject(e)
        }
    }, 100)
})
p1.then(res => {
    console.log(res); // 1
    return p2
}).then(res => {
    console.log(res); // 2
}).catch(err => {

})

第三步欣孤、完善then方法

根據(jù)上面的例子我們來補充then方法里面的內(nèi)容

// then方法接收兩個參數(shù),onResolved昔逗,onRejected降传,分別為Promise成功或失敗后的回調(diào)
class MyPromise {
    // ....
    then(onResolved, onRejected) {
        // 根據(jù)標準,如果then的參數(shù)不是function勾怒,則我們需要忽略它婆排,此處以如下方式處理
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => reason

        // 如果promise1(此處即為this)的狀態(tài)已經(jīng)確定并且是resolved,我們調(diào)用onResolved
        // 因為考慮到有可能throw笔链,所以我們將其包在try/catch塊里
        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onResolved(this.data)
                     // 如果onResolved的返回值是一個Promise對象段只,直接取它的結(jié)果做為返回promise實例的結(jié)果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    resolve(result) // 否則,以它的返回值做為返回promise實例的結(jié)果
                } catch (e) {
                    reject(e)
                }
            })
        }
        // rejected狀態(tài)的處理方法與上面基本一致
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onRejected(this.data)
                     // 如果onRejected的返回值是一個Promise對象卡乾,直接取它的結(jié)果做為返回promise實例的結(jié)果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }
            })
        }
        if (this.status === 'pending') {
            /**
            * 如果當前的Promise還處于pending狀態(tài)翼悴,我們并不能確定調(diào)用onResolved還是onRejected,
            * 只能等到Promise的狀態(tài)確定后,才能確實如何處理鹦赎。
            * 所以我們需要把以上兩種情況的處理邏輯做為callback放入promise1(此處即this)的回調(diào)數(shù)組里
            * 具體邏輯也與上面類似
            */
            return new MyPromise((resolve, reject) => {
                this.onResolvedCallback.push((value) => {
                    try {
                        let result = onResolved(this.data);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
                this.onRejectedCallback.push(reason => {
                    try {
                        let result = onRejected(this.data)
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    }
}

第四步谍椅、完成catch方法
以上我們就實現(xiàn)了一個promise對象的基本功能,最后加上catch方法完整代碼如下


/*
    Promise構(gòu)造函數(shù)接收一個executor函數(shù)古话, executor函數(shù)執(zhí)行完同步或異步操作后雏吭,調(diào)用它的兩個參數(shù)resolve和reject
    如果操作成功,調(diào)用resolve并傳入value
    如果操作失敗陪踩,調(diào)用reject并傳入reason
*/
class MyPromise {
    constructor(executor) {
        if(typeof executor !== 'function') {
            throw new Error('MyPromise must accept a function as a parameter')
        }
        // Promise當前的狀態(tài)
        this.status = 'pending'
        // Promise的值
        this.data = undefined
        // Promise resolve時的回調(diào)函數(shù)集杖们,因為在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
        this.onResolvedCallback = []
        // Promise reject時的回調(diào)函數(shù)集,因為在Promise結(jié)束之前有可能有多個回調(diào)添加到它上面
        this.onRejectedCallback = []
        /*
            考慮到執(zhí)行executor的過程中有可能出錯肩狂,所以我們用try/catch塊給包起來摘完,
            并且在出錯后以catch到的值reject掉這個Promise,另外因為resolve和reject在外部調(diào)用故需要綁定this
        */
        try {
            executor(this.resolve.bind(this), this.reject.bind(this))
        } catch (err) {
            this.reject(err)
        }
    }

    resolve(value) {
        // 成功時將狀態(tài)改為fulfilled
        if(this.status === 'padding') {
            // 如果傳入的值是一個promise實例,則必須等待該Promise對象狀態(tài)改變后,
            // 當前Promsie的狀態(tài)才會改變傻谁,且狀態(tài)取決于參數(shù)Promsie對象的狀態(tài)
            if(value instanceof MyPromise) {
                value.then(res => {
                    this.data = res
                    this.status = 'fulfilled'
                    //執(zhí)行resolve的回調(diào)函數(shù)孝治,將value傳遞到callback中
                    this.onResolvedCallback.forEach(callback => callback(res))
                }, err => {
                    this.data = err
                    this.status = 'rejected'
                    //執(zhí)行reject的回調(diào)函數(shù),將reason傳遞到callback中
                    this.onRejectedCallback.forEach(callback => callback(err))
                })
            } else {
                this.status = 'fulfilled';
                this.data = value;
                //執(zhí)行resolve的回調(diào)函數(shù)审磁,將value傳遞到callback中
                this.onResolvedCallback.forEach(callback => callback(value))
            }
        }
    }
    reject(reason) {
        // 失敗時將狀態(tài)改為rejected
        if(this.status === 'padding') {
            this.status = 'rejected'
            this.data = reason;
            // 觸發(fā)所有的回調(diào)函數(shù)
            this.onRejectedCallback.forEach(item => {
                item(reason)
            })
        }
    }
    then(onResolved, onRejected) {
        // 根據(jù)標準谈飒,如果then的參數(shù)不是function,則我們需要忽略它态蒂,此處以如下方式處理
        onResolved = typeof onResolved === 'function' ? onResolved : value => value
        onRejected = typeof onRejected === 'function' ? onRejected : reason => reason

        // 如果promise1(此處即為this)的狀態(tài)已經(jīng)確定并且是resolved杭措,我們調(diào)用onResolved
        // 因為考慮到有可能throw,所以我們將其包在try/catch塊里
        if (this.status === 'fulfilled') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onResolved(this.data)
                     // 如果onResolved的返回值是一個Promise對象钾恢,直接取它的結(jié)果做為返回promise實例的結(jié)果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                    resolve(result) // 否則手素,以它的返回值做為返回promise實例的結(jié)果
                } catch (e) {
                    reject(e)
                }
            })
        }
        // rejected狀態(tài)的處理方法與上面基本一致
        if (this.status === 'rejected') {
            return new MyPromise((resolve, reject) => {
                try {
                    let result = onRejected(this.data)
                     // 如果onRejected的返回值是一個Promise對象,直接取它的結(jié)果做為返回promise實例的結(jié)果
                    if(result instanceof MyPromise) {
                        result.then(resolve, reject)
                    }
                } catch (e) {
                    reject(e)
                }
            })
        }
        if (this.status === 'pending') {
            /**
            * 如果當前的Promise還處于pending狀態(tài)赘那,我們并不能確定調(diào)用onResolved還是onRejected刑桑,
            * 只能等到Promise的狀態(tài)確定后,才能確實如何處理募舟。
            * 所以我們需要把以上兩種情況的處理邏輯做為callback放入promise1(此處即this)的回調(diào)數(shù)組里
            * 具體邏輯也與上面類似
            */
            return new MyPromise((resolve, reject) => {
                this.onResolvedCallback.push((value) => {
                    try {
                        let result = onResolved(this.data);
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
                this.onRejectedCallback.push(reason => {
                    try {
                        let result = onRejected(this.data)
                        if (result instanceof MyPromise) {
                            result.then(resolve, reject)
                        }
                    } catch (e) {
                        reject(e)
                    }
                })
            })
        }
    }
    catch(onRejected) {
        return this.then(null, onRejected)
    }
}

以上就實現(xiàn)了Promise對象的核心功能,其他的幾個特性很容易實現(xiàn)在此不做贅述闻察,由于本人水平有限拱礁,如有錯漏之處請指出斧正。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末辕漂,一起剝皮案震驚了整個濱河市呢灶,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钉嘹,老刑警劉巖鸯乃,帶你破解...
    沈念sama閱讀 211,817評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異跋涣,居然都是意外死亡缨睡,警方通過查閱死者的電腦和手機鸟悴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,329評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來奖年,“玉大人细诸,你說我怎么就攤上這事÷兀” “怎么了震贵?”我有些...
    開封第一講書人閱讀 157,354評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長水评。 經(jīng)常有香客問我猩系,道長,這世上最難降的妖魔是什么中燥? 我笑而不...
    開封第一講書人閱讀 56,498評論 1 284
  • 正文 為了忘掉前任蝙眶,我火速辦了婚禮,結(jié)果婚禮上褪那,老公的妹妹穿的比我還像新娘幽纷。我一直安慰自己,他們只是感情好博敬,可當我...
    茶點故事閱讀 65,600評論 6 386
  • 文/花漫 我一把揭開白布友浸。 她就那樣靜靜地躺著,像睡著了一般偏窝。 火紅的嫁衣襯著肌膚如雪收恢。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,829評論 1 290
  • 那天祭往,我揣著相機與錄音伦意,去河邊找鬼。 笑死硼补,一個胖子當著我的面吹牛驮肉,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播已骇,決...
    沈念sama閱讀 38,979評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼离钝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了褪储?” 一聲冷哼從身側(cè)響起卵渴,我...
    開封第一講書人閱讀 37,722評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鲤竹,沒想到半個月后浪读,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,189評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,519評論 2 327
  • 正文 我和宋清朗相戀三年碘橘,在試婚紗的時候發(fā)現(xiàn)自己被綠了互订。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,654評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡蛹屿,死狀恐怖屁奏,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情错负,我是刑警寧澤坟瓢,帶...
    沈念sama閱讀 34,329評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站犹撒,受9級特大地震影響折联,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜识颊,卻給世界環(huán)境...
    茶點故事閱讀 39,940評論 3 313
  • 文/蒙蒙 一诚镰、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祥款,春花似錦清笨、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,762評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至桨昙,卻和暖如春检号,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背蛙酪。 一陣腳步聲響...
    開封第一講書人閱讀 31,993評論 1 266
  • 我被黑心中介騙來泰國打工齐苛, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人桂塞。 一個月前我還...
    沈念sama閱讀 46,382評論 2 360
  • 正文 我出身青樓凹蜂,卻偏偏與公主長得像,于是被迫代替她去往敵國和親藐俺。 傳聞我的和親對象是個殘疾皇子炊甲,可洞房花燭夜當晚...
    茶點故事閱讀 43,543評論 2 349

推薦閱讀更多精彩內(nèi)容