手寫 Promise

前言

都 2020 年了,Promise 大家肯定都在用了陌兑,但是估計(jì)很多人對(duì)其原理還是一知半解允瞧,今天就讓我們一起實(shí)現(xiàn)一個(gè)符合 PromiseA+ 規(guī)范的 Promise。

簡單版

我們都知道 Promise 的調(diào)用方式泌绣,new Promise(executor)钮追, executor 兩個(gè)參數(shù)预厌,resolve,reject元媚。所以現(xiàn)在我們的代碼長這樣

class Promise {
    constructor(executor) {
       const resolve = () => {}
       const reject = () => {}
       executor(resolve, rejcet)
    }
}
Promise 內(nèi)部有三個(gè)狀態(tài)轧叽,pending、fulfilled刊棕、rejected炭晒,初始是 pending,調(diào)用 resolve 后變?yōu)?fulfilled,甥角,調(diào)用 reject 后變?yōu)?rejected网严。fulfilled 時(shí)會(huì)調(diào)用 then 注冊(cè)的成功的回調(diào),rejected 時(shí)會(huì)調(diào)用 then 注冊(cè)的失敗的回調(diào)嗤无。

// Promise 內(nèi)部狀態(tài)
const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }

class Promise {
    constructor(executor) {
        this.status = STATUS.PENDING;
        this.value = undefined; // 成過的值
        this.reason = undefined; // 失敗的值
        const resolve = (val) => {
            if (this.status == STATUS.PENDING) {
                this.status = STATUS.FUFILLED;
                this.value = val;
            }
        }
        const reject = (reason) => {
            if (this.status == STATUS.PENDING) {
                this.status = STATUS.REJECTED;
                this.reason = reason;
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            // 出錯(cuò)走失敗邏輯
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        if (this.status == STATUS.FUFILLED) {
            onFulfilled(this.value);
        }
        if (this.status == STATUS.REJECTED) {
            onRejected(this.reason);
        }
    }
}

現(xiàn)在我們的 Promise 已經(jīng)初步實(shí)現(xiàn)了震束,但還有很多問題

一個(gè) promise 可以調(diào)用多次 then 方法怜庸,也就是說可以注冊(cè)多個(gè)回調(diào),所以我們需要一個(gè)隊(duì)列來保存這些回調(diào)垢村。同時(shí)我們沒有對(duì) pending 狀態(tài)的 then 方法做處理割疾,當(dāng) promise 為 pending 狀態(tài)時(shí),then 方法應(yīng)該將回調(diào)放入到隊(duì)列當(dāng)中嘉栓,而不是直接運(yùn)行宏榕。所以改進(jìn)之后的代碼如下。

const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
class Promise {
    constructor(executor) {
        this.status = STATUS.PENDING;
        this.value = undefined; // 成過的值
        this.reason = undefined; // 失敗的值
+       this.onResolvedCallbacks = []; // 存放成功的回調(diào)的 
+       this.onRejectedCallbacks = []; // 存放失敗的回調(diào)的
        const resolve = (val) => {
            if (this.status == STATUS.PENDING) {
                this.status = STATUS.FUFILLED;
                this.value = val;
                // 成功時(shí)調(diào)用成功隊(duì)列里的回調(diào)
+                this.onResolvedCallbacks.forEach(fn=>fn());
            }
        }
        const reject = (reason) => {
            if (this.status == STATUS.PENDING) {
                this.status = STATUS.REJECTED;
                this.reason = reason;
                // 失敗時(shí)調(diào)用失敗隊(duì)列里的回調(diào)
+               this.onRejectedCallbacks.forEach(fn=>fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            // 出錯(cuò)走失敗邏輯
            reject(e)
        }
    }
    then(onFulfilled, onRejected) {
        if (this.status === STATUS.FUFILLED) {
            onFulfilled(this.value);
        }
        if (this.status === STATUS.REJECTED) {
            onRejected(this.reason);
        }
+       if (this.status === STATUS.PENDING) {
+           this.onResolvedCallbacks.push(()=>{ // todo..
+              onFulfilled(this.value);
+           })
+           this.onRejectedCallbacks.push(()=>{ // todo..
+               onRejected(this.reason);
+           })
+       }
+   }
}

到這一個(gè)簡單 promise 80%的功能已經(jīng)實(shí)現(xiàn)了侵佃,但是還有一個(gè)問題麻昼,promise 可以鏈?zhǔn)秸{(diào)用,也就是我們巢霰玻看到的 promise.then().then()涌献。所以我們得在 then 方法里去返回一個(gè)新的 promise。

const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }
class Promise {
   // 上面邏輯省略
    ...
    then(onFulfilled, onRejected) { // swtich  作用域
+        let promise2 = new Promise((resolve, reject) => {
+            if (this.status === STATUS.FUFILLED) {
+                // to....
+                try {
+                    let x = onFulfilled(this.value);
+                    resolve(x);
+                } catch (e) {
+                    reject(e);
+                }
+            }
+            if (this.status === STATUS.REJECTED) {
+                try {
+                    let x = onRejected(this.reason);
+                    resolve(x);
+                } catch (e) {
+                    reject(e);
+                }
+            }
+            if (this.status === STATUS.PENDING) {
+                this.onResolvedCallbacks.push(() => { // todo..
+                    try {
+                        let x = onFulfilled(this.value);
+                        resolve(x);
+                    } catch (e) {
+                        reject(e);
+                    }
+                })
+                this.onRejectedCallbacks.push(() => { // todo..
+                    try {
+                        let x = onRejected(this.reason);
+                        resolve(x);
+                    } catch (e) {
+                        reject(e);
+                    }
+
+                })
+            }
+        })
+
+        return promise2;
+    }
}

我們注意到我們把回調(diào)的執(zhí)行邏輯都放到了 promise2 的內(nèi)部首有,之所以這樣做燕垃,是因?yàn)槲覀冃枰?onFufilled 的返回值去 resolve promise2,這也是為什么 then 回調(diào)的返回值會(huì)傳給下一個(gè) then 的原因井联。

完整版

上面的 promise 與規(guī)范有一些差距

then 注冊(cè)的回調(diào)都是異步執(zhí)行的

如果 then 注冊(cè)回調(diào)的返回值是個(gè)函數(shù)或?qū)ο蟛泛荆@里處理起來會(huì)復(fù)雜一點(diǎn),我們先看看規(guī)范是怎么定義的

promise2 = promise1.then(onFulfilled, onRejected);

x = onFulfilled 或 onRejected 的返回值

  • 2.3.1 如果promise和x引用同一個(gè)對(duì)象烙常,則用TypeError作為原因拒絕(reject)promise轴捎。
  • 2.3.2 如果x是一個(gè)promise,采用promise的狀態(tài)
  • 2.3.2.1 如果x是請(qǐng)求狀態(tài)(pending),promise必須保持pending直到xfulfilled或rejected
  • 2.3.2.2 如果x是完成態(tài)(fulfilled),用相同的值完成fulfillpromise
  • 2.3.2.2 如果x是拒絕態(tài)(rejected)蚕脏,用相同的原因rejectpromise
  • 2.3.3另外侦副,如果x是個(gè)對(duì)象或者方法
  • 2.3.3.1 讓x作為x.then.
  • 2.3.3.2 如果取回的x.then屬性的結(jié)果為一個(gè)異常e,用e作為原因reject promise
  • 2.3.3.3 如果then是一個(gè)方法,把x當(dāng)作this來調(diào)用它驼鞭,第一個(gè)參數(shù)為resolvePromise秦驯,第二個(gè)參數(shù)為rejectPromise,其中:
  • 2.3.3.3.1 如果/當(dāng) resolvePromise 被一個(gè)值 y 調(diào)用,運(yùn)行 [[Resolve]](promise, y)
  • 2.3.3.3.2 如果/當(dāng) rejectPromise 被一個(gè)原因 r 調(diào)用挣棕,用 r 拒絕(reject)promise
  • 2.3.3.3.3 如果 resolvePromise 和 rejectPromise 都被調(diào)用译隘,或者對(duì)同一個(gè)參數(shù)進(jìn)行多次調(diào)用,第一次調(diào)用執(zhí)行洛心,任何進(jìn)一步的調(diào)用都被忽略
  • 2.3.3.3.4 如果調(diào)用 then 拋出一個(gè)異常 e
  • 2.3.3.3.4.1 如果 resolvePromise 或 rejectPromise 已被調(diào)用固耘,忽略。
  • 2.3.3.3.4.2 或者词身, 用 e 作為reason拒絕(reject)promise

規(guī)范可能有點(diǎn)復(fù)雜厅目,需要自己慢慢消化,這里我直接把代碼貼出來,我會(huì)在代碼里標(biāo)注每個(gè)規(guī)范的實(shí)現(xiàn)點(diǎn)损敷。

const STATUS = { PENDING: 'PENDING', FUFILLED: 'FUFILLED', REJECTED: 'REJECTED' }

// 我們的promise 按照規(guī)范來寫 就可以和別人的promise公用
function resolvePromise(x, promise2, resolve, reject) {
    // 規(guī)范 2.3.1
    if (promise2 == x) { // 防止自己等待自己完成
        return reject(new TypeError('出錯(cuò)了'))
    }
    // 規(guī)范 2.3.3
    if ((typeof x === 'object' && x !== null) || typeof x === 'function') {
        // x可以是一個(gè)對(duì)象 或者是函數(shù)
        let called;
        try {
            // 規(guī)范 2.3.3.1
            let then = x.then;
            if (typeof then == 'function') {
                // 2.3.3.3
                then.call(x, function(y) {
                    // 規(guī)范 2.3.3.3.3
                    if (called) return
                    called = true;
                    // 規(guī)范 2.3.3.3.1
                    resolvePromise(y, promise2, resolve, reject);
                }, function(r) {
                    // 規(guī)范 2.3.3.3.3
                    if (called) return
                    called = true;
                    // 規(guī)范 2.3.3.3.2
                    reject(r);
                })
            } else {
                resolve(x); // 此時(shí)x 就是一個(gè)普通對(duì)象
            }
        } catch (e) {
            // 規(guī)范 2.3.3.3.4.1
            if (called) return
            called = true;
            // 規(guī)范 2.3.3.3.4 
            reject(e); // 取then時(shí)拋出錯(cuò)誤了
        }
    } else {
        resolve(x); // x是一個(gè)原始數(shù)據(jù)類型 不能是promise
    }
    // 不是proimise 直接就調(diào)用resolve
}
class Promise {
    constructor(executor) {
        this.status = STATUS.PENDING;
        this.value = undefined;
        this.reason = undefined;
        this.onResolvedCallbacks = []; // 存放成功的回調(diào)的 
        this.onRejectedCallbacks = []; // 存放失敗的回調(diào)的
        const resolve = (val) => {
            if(val instanceof Promise){ // 是promise 就繼續(xù)遞歸解析
                return val.then(resolve,reject)
            }

            if (this.status == STATUS.PENDING) {
                this.status = STATUS.FUFILLED;
                this.value = val;
                // 發(fā)布
                this.onResolvedCallbacks.forEach(fn => fn());
            }
        }
        const reject = (reason) => {
            if (this.status == STATUS.PENDING) {
                this.status = STATUS.REJECTED;
                this.reason = reason;
                // 腹部
                this.onRejectedCallbacks.forEach(fn => fn());
            }
        }
        try {
            executor(resolve, reject);
        } catch (e) {
            // 出錯(cuò)走失敗邏輯
            reject(e)
        }
    }
    then(onFulfilled, onRejected) { // swtich  作用域
        // 可選參數(shù)
        onFulfilled = typeof onFulfilled === 'function' ? onFulfilled : x => x
        onRejected = typeof onRejected === 'function'? onRejected: err=> {throw err}
        let promise2 = new Promise((resolve, reject) => {
            if (this.status === STATUS.FUFILLED) {
                // to....
                setTimeout(() => {
                    try {
                        let x = onFulfilled(this.value);
                        resolvePromise(x, promise2, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === STATUS.REJECTED) {
                setTimeout(() => {
                    try {
                        let x = onRejected(this.reason);
                        resolvePromise(x, promise2, resolve, reject)
                    } catch (e) {
                        reject(e);
                    }
                }, 0);
            }
            if (this.status === STATUS.PENDING) {
                // 裝飾模式 切片編程
                this.onResolvedCallbacks.push(() => { // todo..
                    setTimeout(() => {
                        try {
                            let x = onFulfilled(this.value);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);
                })
                this.onRejectedCallbacks.push(() => { // todo..
                    setTimeout(() => {
                        try {
                            let x = onRejected(this.reason);
                            resolvePromise(x, promise2, resolve, reject)
                        } catch (e) {
                            reject(e);
                        }
                    }, 0);

                })
            }
        });
        return promise2;
    }
}

測試工具

給大家推薦一個(gè)測試 promise 是否規(guī)范的工具 --- promises-aplus-tests户辫,使用方法如下
全局安裝 promises-aplus-tests,然后添加以下代碼

Promise.deferred = function () {
    let dfd = {};
    dfd.promise = new Promise((resolve,reject)=>{
        dfd.resolve = resolve;
        dfd.reject = reject
    })
    return dfd;
}
module.exports = Promise

然后直接在在控制臺(tái)運(yùn)行 promises-aplus-tests <當(dāng)前 promise 代碼地址>

可以看到我們的 promise 是順利通過測試的嗤锉。


image.png

總結(jié)

希望這篇文章可以幫助大家更深入的理解 promise
碼字不易渔欢,期望得到你的贊。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末瘟忱,一起剝皮案震驚了整個(gè)濱河市奥额,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌访诱,老刑警劉巖垫挨,帶你破解...
    沈念sama閱讀 211,743評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異触菜,居然都是意外死亡九榔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,296評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門涡相,熙熙樓的掌柜王于貴愁眉苦臉地迎上來哲泊,“玉大人,你說我怎么就攤上這事催蝗∏型” “怎么了?”我有些...
    開封第一講書人閱讀 157,285評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵丙号,是天一觀的道長先朦。 經(jīng)常有香客問我,道長犬缨,這世上最難降的妖魔是什么喳魏? 我笑而不...
    開封第一講書人閱讀 56,485評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮怀薛,結(jié)果婚禮上刺彩,老公的妹妹穿的比我還像新娘。我一直安慰自己乾戏,他們只是感情好迂苛,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,581評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鼓择,像睡著了一般。 火紅的嫁衣襯著肌膚如雪就漾。 梳的紋絲不亂的頭發(fā)上呐能,一...
    開封第一講書人閱讀 49,821評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼摆出。 笑死朗徊,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的偎漫。 我是一名探鬼主播爷恳,決...
    沈念sama閱讀 38,960評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼象踊!你這毒婦竟也來了温亲?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 37,719評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤杯矩,失蹤者是張志新(化名)和其女友劉穎栈虚,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體史隆,經(jīng)...
    沈念sama閱讀 44,186評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡魂务,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,516評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了泌射。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片粘姜。...
    茶點(diǎn)故事閱讀 38,650評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖熔酷,靈堂內(nèi)的尸體忽然破棺而出相艇,到底是詐尸還是另有隱情,我是刑警寧澤纯陨,帶...
    沈念sama閱讀 34,329評(píng)論 4 330
  • 正文 年R本政府宣布坛芽,位于F島的核電站,受9級(jí)特大地震影響翼抠,放射性物質(zhì)發(fā)生泄漏咙轩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,936評(píng)論 3 313
  • 文/蒙蒙 一阴颖、第九天 我趴在偏房一處隱蔽的房頂上張望活喊。 院中可真熱鬧,春花似錦量愧、人聲如沸钾菊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,757評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽煞烫。三九已至,卻和暖如春累颂,著一層夾襖步出監(jiān)牢的瞬間滞详,已是汗流浹背凛俱。 一陣腳步聲響...
    開封第一講書人閱讀 31,991評(píng)論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留料饥,地道東北人蒲犬。 一個(gè)月前我還...
    沈念sama閱讀 46,370評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像岸啡,于是被迫代替她去往敵國和親原叮。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,527評(píng)論 2 349