這次徹底搞懂 Promise(手寫源碼多注釋篇)
作者:一陣風,一枚只想安靜寫代碼的程序員,來自程序員成長指北交流群? ??github: https://github.com/yizhengfeng-jj/promise
前言
promise 是 js 里面非常重要的一部分,搞懂了 promise 才能更好的去理解 async,await 和 generator。但是往往很多時候就是不理解 promise 的機制,所以這次通過一步步實現(xiàn)一個 promise 來加深自己的印象,提高自己的思維。
大概的架子
通過我們經(jīng)常寫的 promise 語法,我們可以先寫一個大概的架子出來,promise 接受回調(diào),并且調(diào)用,自身帶有三種狀態(tài),pendding, onFulfilled, onRejected,并且 resolve 這個函數(shù)可以讓 pendding 狀態(tài)變成 onFulfilled 狀態(tài),同理 reject 函數(shù)可以讓 pendding 狀態(tài)變成 onRejected 狀態(tài)。我們先把上面描述部分實現(xiàn)了。
const?PromiseCopy?=?function?(fn)?{
??this.info?=?{
????status:?"pending",
????value:?"",
??};
??const?self?=?this;
??self.onFulfilledArr?=?[];?//?then函數(shù)里面的第一個回調(diào)函數(shù)的集合
??self.onRejectedArr?=?[];?//?then函數(shù)里面的第二個回調(diào)函數(shù)的集合
??const?resolve?=?function?(value)?{
????//?加這個判斷是為了表示,只有在pendding狀態(tài)下才會去執(zhí)行
????//?狀態(tài)已經(jīng)變成onFulfilled之后就不能再去改變了
????//?符合PromiseA+中的2.1.2.1
????if?(self.info.status?===?"pending")?{
??????self.info.status?=?"onFulfilled";
??????self.info.value?=?value;
??????self.onFulfilledArr.forEach((fn)?=>?fn(value));
????}
??};
??//?和上面同理符合PromiseA+,2.1.3.1
??const?reject?=?function?(value)?{
????if?(self.info.status?===?"pending")?{
??????self.info.status?=?"onRejected";
??????self.info.value?=?value;
??????self.onRejectedArr.forEach((fn)?=>?fn(value));
????}
??};
??fn(resolve,?reject);
};
resolve 的附加實現(xiàn)
其實寫到這里我們的 resolve 函數(shù)還是有一些功能沒有實現(xiàn)的, 我們知道 調(diào)用 resolve(x), x 的值有好幾種情況,如下
如果 x 是 Promise 實例本身,則拋出錯誤 如果 x 是一個 Promise 對象,那么 then 函數(shù)的執(zhí)行取決這個 x 的狀態(tài),如果 x 也調(diào)用 resolve(y),其中 y 也是一個 promise 對象.那么 then 函數(shù)的執(zhí)行取決于這個 promise 對象,依次類推,直到最后一個 promise 狀態(tài)更改 如果 x 是一個 thenable 對象,就是一個對象包含 then 這個屬性,或者是一個函數(shù)包含一個 then 的靜態(tài)方法,那么直接執(zhí)行 then 函數(shù) 如果 x 是一個普通值,直接變成 onFulfilled 狀態(tài),執(zhí)行后面的 then 函數(shù)
思考
網(wǎng)上實現(xiàn)的大部分 resolve 都是我上面的代碼,但是根據(jù)規(guī)范,resolve 函數(shù)里面應該是要判斷上面幾點的,所以我們上面寫的代碼是有誤的 還有一個問題是,我們需要在 resolve 函數(shù)里面判斷 x 是不是實例的本身,但是正常的 resolve 函數(shù)我們經(jīng)常是傳入一個參數(shù),所以中間肯定是有一個中間函數(shù)的,看下面的代碼
const?PromiseCopy?=?function?(fn)?{
??this.info?=?{
????status:?"pending",
????value:?"",
??};
??const?self?=?this;
??self.onFulfilledArr?=?[];?//?then函數(shù)里面的第一個回調(diào)函數(shù)的集合
??self.onRejectedArr?=?[];?//?then函數(shù)里面的第二個回調(diào)函數(shù)的集合
??//?_resolve?是我們經(jīng)常調(diào)用的resolve
??//?但是真正實現(xiàn)的應該是里面的resolve
??const?_resolve?=?function?(value)?{
????//?這個函數(shù)得改變一下
????//?PromiseCopy一旦被實例化,那么self就是實例本身了
????resolve(self,?value);
??};
??//?此時我們就可以在resolve進行判斷了
??const?resolve?=?function?(promise,?value)?{
????let?ifexec?=?false;
????//?首先判斷value是不是promise本身
????if?(value?===?promise)?{
??????//?一定要用TypeError寫?不然promises-aplus-tests跑不通
??????//?切記這是第一個坑,promises-aplus-tests只認TypeError這種錯誤形式
??????reject(new?TypeError("A?promise?cannot?be?onFulfilled?with?itself."));
????}
????//?value是一個thenable對象
????//?這個要Object.prototype.toString.call(value)?===?"[object?Object]"判斷
????//?不然resolve([])有問題,不知道是不是我實現(xiàn)問題
????if?(
??????value?&&
??????(Object.prototype.toString.call(value)?===?"[object?Object]"?||
????????typeof?value?===?"function")
????)?{
??????//?var?promise1?=?Promise.resolve(dump).then(function?()?{
??????//???return?{
??????//?????then:?(resolve,?reject)?=>?{
??????//???????setTimeout(()?=>?{
??????//?????????resolve({
??????//???????????then:?(resolve,?reject)?=>?{
??????//?????????????resolve("aa111a");
??????//?????????????throw?"other";
??????//???????????},
??????//?????????});
??????//???????});
??????//?????},
??????//???};
??????//?});
??????//?promise1.then(
??????//???(res)?=>?{
??????//?????console.log(res?===?"aa111a");
??????//?????console.log("aaa");
??????//???},
??????//???(res)?=>?{
??????//?????console.log(res);
??????//?????console.log("error");
??????//???}
??????//?);
??????//?這里的try--catch一定要加?,不然會promises-aplus-tests會一直報錯,這是第三個大坑
??????//?因為promises-aplus-test測試里面有這一條的
??????//?看上面注釋例子
??????try?{
????????//?拿到then函數(shù)
????????const?then?=?value.then;
????????//?如果then是一個函數(shù)則執(zhí)行這個函數(shù)
????????if?(typeof?then?===?"function")?{
??????????//?為什么要.call(value,?x,?y)?你們可以自己試一下原生的Promise在這種情況下this指向的就是value,所以要綁定
??????????//?因為then我們已經(jīng)拿出來了then?=?value.then,直接調(diào)用then(),this就指向的window
??????????//?為什么后面還需要綁定兩個函數(shù)了
??????????//?根據(jù)原生的Promise可知,thenable中的then函數(shù)可以接受兩個函數(shù)resolve,reject
??????????//?只有手動調(diào)用了resolve和reject才會執(zhí)行后面的.then操作,具體大家自己操作下
??????????then.call(
????????????value,
????????????function?(value)?{
??????????????if?(ifexec)?{
????????????????return;
??????????????}
??????????????//?ifexec這個一定要加,不然也會報200ms錯誤,第四個大坑
??????????????//?目的是為了不讓多次執(zhí)行,語言無法表達看下面的例子
??????????????//?var?promise1?=?Promise.resolve(dump).then(function?()?{
??????????????//???return?{
??????????????//?????then:?(resolve,?reject)?=>?{
??????????????//???????resolve("aa111a");
??????????????//???????resolve("aa111a");
??????????????//?????},
??????????????//???};
??????????????//?});
??????????????ifexec?=?true;
??????????????resolve(promise,?value);
????????????},
????????????function?(value)?{
??????????????if?(ifexec)?{
????????????????return;
??????????????}
??????????????ifexec?=?true;
??????????????reject(value);
????????????}
??????????);
??????????return;
????????}
??????}?catch?(e)?{
????????if?(ifexec)?{
??????????return;
????????}
????????ifexec?=?true;
????????reject(e);
??????}
????}
????//?下面這一點非常的重要,是async,await?和一些插件比如saga的核心
????//?就是如果x是一個promise對象,那么then的執(zhí)行取決于x的狀態(tài)
????//?還有這一個判斷一定要放在這里,不要和上面的換?不然promises-aplus-tests會報一個超過200ms的錯誤,切記這是第二個坑
????if?(value?&&?value?instanceof?PromiseCopy?&&?value.then?===?promise.then)?{
??????//?將promise的onFulfilledArr給到value
??????//?但是還沒有那么簡單我們要明白兩點
??????//?如果value這個promise已經(jīng)不是pendding,我們給了他也沒有用,所以需要直接調(diào)用
??????if?(value.info.status?===?"pending")?{
????????value.onFulfilledArr?=?self.onFulfilledArr;
????????value.onRejectedArr?=?self.onRejectedArr;
??????}
??????//?如果value狀態(tài)是onFulfilled
??????if?(value.info.status?===?"onRejected")?{
????????self.info.value?=?value.info.value;
????????self.onRejectedArr.forEach((fn)?=>?fn(value.info.value));
??????}
??????//?如果value狀態(tài)是reject
??????if?(value.info.status?===?"onFulfilled")?{
????????self.info.value?=?value.info.value;
????????self.onFulfilledArr.forEach((fn)?=>?fn(value.info.value));
??????}
??????return;
????}
????//?如果是一個普通的值
????//?加這個判斷是為了表示,只有在pendding狀態(tài)下才會去執(zhí)行
????//?狀態(tài)已經(jīng)變成onFulfilled之后就不能再去改變了
????//?符合PromiseA+中的2.1.2.1
????if?(self.info.status?===?"pending")?{
??????self.info.status?=?"onFulfilled";
??????self.info.value?=?value;
??????self.onFulfilledArr.forEach((fn)?=>?fn(value));
????}
??};
??//?和上面同理符合PromiseA+,2.1.3.1
??//?reject沒有resolve那么多規(guī)則,比較簡單
??const?reject?=?function?(value)?{
????if?(self.info.status?===?"pending")?{
??????self.info.status?=?"onRejected";
??????self.info.value?=?value;
??????self.onRejectedArr.forEach((fn)?=>?fn(value));
????}
??};
??//?此時fn調(diào)用的是_reoslve
??//?這個try?catch主要是實現(xiàn)promiseCopy.prototype.catch
??try?{
????fn(_resolve,?reject);
??}?catch?(e)?{
????setTimeout(()?=>?{
??????self.onRejectedArr.forEach((fn)?=>?fn(e));
????});
??}
};
then 的實現(xiàn)
我們上面介紹的是 promise 的 resolve 用法,promise 還有一個基本用法就是后面接 then,因為是.then 所以我們想到的是這個 then 方法掛在到原型上的,那么 new PromiseCopy 的時候就可以得到這個 then。then 里面是兩個函數(shù),一個是 onFulfilled 后執(zhí)行的回調(diào),一個是 onRejected 后執(zhí)行的回調(diào)。現(xiàn)在的問題是他是怎么做到 then 里面的函數(shù)是在 resolve 和 reject 后執(zhí)行的?這種推遲執(zhí)行或者說在某種情況下去執(zhí)行我們想到的就是觀察者模式了。下面用代碼把上面的話實現(xiàn)一遍,在代碼里面會寫詳細一點的注釋。
PromiseCopy.prototype.then?=?function?(onFulfilled,?onRejected)?{
??const?self?=?this;
??//?這里要判斷下,如果PromiseCopy是resolve了那么就直接執(zhí)行onFulfilled
??if?(self.info.status?===?"onFulfilled")?{
????setTimeout(()?=>?{
??????onFulfilled(self.info.value);
????});
??}
??if?(self.info.status?===?"onRejected")?{
????setTimeout(()?=>?{
??????onRejected(self.info.value);
????});
??}
??//?根據(jù)PromiseA+中的2.2.1.1和2.2.1.2,onFulfilled和onRejected必須是函數(shù),不然就會被忽略
??if?(typeof?onFulfilled?===?"function")?{
????self.onFulfilledArr.push(()?=>?{
??????setTimeout(()?=>?{
????????onFulfilled(self.info.value);
??????});
????});
??}
??if?(typeof?onRejected?===?"function")?{
????self.onRejectedArr.push(()?=>?{
??????setTimeout(()?=>?{
????????onRejected(self.info.value);
??????});
????});
??}
??//?根據(jù)PromiseA+?2.2.7規(guī)范?then函數(shù)必須返回一個promise對象
??return?new?PromiseCopy((resolve,?reject)?=>?{});
};
then 的額外實現(xiàn)
上面實現(xiàn)的 then 也是一個簡單的用法,不過根據(jù) PromiseA+的規(guī)范這個 then 函數(shù)還有幾個點沒有實現(xiàn),看代碼解釋
promise2 = promise1.then(onFulfilled, onRejected);
promise2.then(onFulfilled, onRejected);
promise1.then 中的 onFulfilled,onRejected 函數(shù)如果返回一個 x,那么當作[[Resolve]](promise2, x)來處理,就跟上面的 resolve 一樣處理,注意如果函數(shù)什么都沒有返回,就是返回的 undefined promise1.then 函數(shù)中的兩個回調(diào)函數(shù)只要有一個報錯,那么直接調(diào)用 promise2.then 函數(shù)中的錯誤回調(diào) 如果 promise1.then 的第一個回調(diào)不是函數(shù),并且 promise1 調(diào)用的是 resolve,那么 promise2.then 的第一個回調(diào)參數(shù)是 promise1 中 resolve 函數(shù)的拋出值 同理,如果 promise1.then 第二個回調(diào)不是函數(shù),并且 promise1 調(diào)用的是 reject,那么 promise2.then 中的錯誤回調(diào)就會執(zhí)行
思考
如果像上面這么說的話,這個新拋出來的 promise 何時調(diào)用這個 resolve 或者 reject 是一個關鍵, 并且這個拋出的 promise 的執(zhí)行還得看 onFulfilled 和 onRejected 返回值,這一點當時寫 promise 的時候想了很久,不知道如何組織,后來實在想不出來,看了下網(wǎng)上很多文章,發(fā)現(xiàn)這些邏輯都是在 PromiseCopy 主體里面實現(xiàn)的。
return new PromiseCopy((resolve, reject) => {});
then 實現(xiàn)加強版
PromiseCopy.prototype.then?=?function?(onFulfilled,?onRejected)?{
??const?self?=?this;
??//?這個一定要這么寫目的為了讓值傳遞
??onFulfilled?=?typeof?onFulfilled?===?"function"???onFulfilled?:?(val)?=>?val;
??//?這個一定要這么寫,一定要拋出一個錯throw?err
??onRejected?=
????typeof?onRejected?===?"function"
????????onRejected
??????:?(err)?=>?{
??????????throw?err;
????????};
??const?newPromise?=?new?PromiseCopy((resolve,?reject)?=>?{
????if?(self.info.status?===?"onFulfilled")?{
??????setTimeout(()?=>?{
????????try?{
??????????//?如果onFulfilled不是一個函數(shù)resolve--self.info.value
??????????let?value?=?self.info.value;
??????????//?這個注釋不要,留著只是為了記錄當時的思路
??????????//?這個加判斷是為了防止then函數(shù)逇回調(diào)不是一個函數(shù),,是一個字符串
??????????//???if?(typeof?onFulfilled?===?"function")?{
??????????//?????value?=?onFulfilled(value);
??????????//???}
??????????value?=?onFulfilled(value);
??????????//?這里要做一個[[Resolve]](promise2,?x)處理了
??????????//?因為resolve里面直接做了,所以直接調(diào)用,和網(wǎng)上的一些實現(xiàn)有點不一樣
??????????//?他們是提取了一個resolvePromise函數(shù)調(diào)用,我是直接調(diào)用了resolve
??????????resolve(value);
????????}?catch?(e)?{
??????????reject(e);
????????}
??????});
????}
????//?注意這里根據(jù)上面可知onFulfilled,onRejected拋出的值都要經(jīng)過[[Resolve]](promise2,?x)
????//?這和resolve,reject不一樣,promise中resolve才走[[Resolve]](promise2,?x),reject不走
????if?(self.info.status?===?"onRejected")?{
??????setTimeout(()?=>?{
????????try?{
??????????let?{?value?}?=?self.info;
??????????value?=?onRejected(self.info.value);
??????????resolve(value);
????????}?catch?(e)?{
??????????reject(e);
????????}
??????});
????}
????//?如果是pending狀態(tài)也需要push
????if?(self.info.status?===?"pending")?{
??????self.onFulfilledArr.push((data)?=>?{
????????setTimeout(()?=>?{
??????????try?{
????????????let?value?=?data;
????????????value?=?onFulfilled(value);
????????????resolve(value);
??????????}?catch?(e)?{
????????????reject(e);
??????????}
????????});
??????});
??????self.onRejectedArr.push((data)?=>?{
????????setTimeout(()?=>?{
??????????try?{
????????????let?value?=?data;
????????????value?=?onRejected(data);
????????????resolve(value);
??????????}?catch?(e)?{
????????????reject(e);
??????????}
????????});
??????});
????}
??});
??return?newPromise;
};
小結
到這里 promise 的主體實現(xiàn)已經(jīng)完成了,下面是測試結果

Promise 其他靜態(tài)方法
Promise.resolve
PromiseCopy.resolve?=?function?(data)?{
??return?new?PromiseCopy((resolve,?reject)?=>?{
????resolve(data);
??});
};
reject
Promise.reject?=?function?(reason)?{
??return?new?Promise((resolve,?reject)?=>?{
????reject(reason);
??});
};
Promise.all
這個方法有幾個特點如下
該方法接受一個數(shù)組,數(shù)組每一個元素都是一個 promise 對象 只有所有 promise 都是 onFulfilled 的時候才會執(zhí)行 then 回調(diào),并且結果順序和數(shù)組的一致 如果其中一個 promise 發(fā)生了 reject 那么就會返回這個值
PromiseCopy.all?=?function?(data)?{
??let?count?=?0;?//?記錄調(diào)用次數(shù)
??let?total?=?data.length;
??let?result?=?[];
??return?new?PromiseCopy((resolve,?reject)?=>?{
????for?(let?i?=?0;?i???????data[i].then(
????????(res)?=>?{
??????????result.push(res);
??????????++count;
??????????if?(count?===?totla)?{
????????????resolve(result);
??????????}
????????},
????????(res)?=>?{
??????????return?reject(res);
????????}
??????);
????}
??});
};
Promise.race
這個方法也有以下幾個特點
這個方法也是接受數(shù)組,數(shù)組的元素是 promise 他只返回最快的那一個 promise 的值 就算有錯誤也會返回最快那一個 promise 的值
PromiseCopy.race?=?function?(data)?{
??const?total?=?data.length;
??return?new?PromiseCopy((resolve,?reject)?=>?{
????for?(let?i?=?0;?i???????data[i].then(
????????(res)?=>?{
??????????resolve(res);
????????},
????????(res)?=>?{
??????????return?reject(res);
????????}
??????);
????}
??});
};
catch 方法
PromiseCopy.prototype.catch?=?function?(onRejected)?{
??//?能到catch里面來的一定是走的reject的
??//?而且狀態(tài)一定是pendding
??const?self?=?this;
??const?newPromise?=?new?PromiseCopy((resolve,?reject)?=>?{
????if?(self.info.status?===?"onRejected")?{
??????try?{
????????setTimeout(()?=>?{
??????????let?{?value?}?=?self.info;
??????????if?(typeof?onRejected?===?"function")?{
????????????value?=?onRejected(self.info.value);
??????????}
??????????resolve(value);
????????});
??????}?catch?(e)?{
????????rejetc(e);
??????}
????}
????if?(self.info.status?===?"pending")?{
??????self.onRejectedArr.push((data)?=>?{
????????setTimeout(()?=>?{
??????????try?{
????????????let?value?=?data;
????????????if?(typeof?onRejected?===?"function")?{
??????????????value?=?onRejected(data);
????????????}
????????????resolve(value);
??????????}?catch?(e)?{
????????????reject(e);
??????????}
????????});
??????});
????}
??});
??return?newPromise;
};
//?后來發(fā)現(xiàn)catch有一個簡單的實現(xiàn)方法
//?沒有刪除上面就是為了記錄思路過程
Promise.prototype.catch?=?function?(onRejected)?{
??return?this.then(null,?onRejected);
};
deferred
這個是 Promise 提供的一個快捷使用,自己實現(xiàn) promise 的時候一定要加,不然 promises-aplus-tests promise.js 跑不過
PromiseCopy.defer?=?PromiseCopy.deferred?=?function?()?{
??let?dfd?=?{};
??dfd.promise?=?new?PromiseCopy((resolve,?reject)?=>?{
????dfd.resolve?=?resolve;
????dfd.reject?=?reject;
??});
??return?dfd;
};
源碼
promise 源碼已經(jīng)上傳到個人 github ( https://github.com/yizhengfeng-jj/promise 歡迎 star)
參考文章
解讀Promise內(nèi)部實現(xiàn)原理
Promise 源碼分析
小邵教你玩轉(zhuǎn)promise源碼
??愛心三連擊
1.看到這里了就點個在看支持下吧,你的「點贊,在看」是我創(chuàng)作的動力。
2.關注公眾號
程序員成長指北,回復「1」加入Node進階交流群!「在這里有好多 Node 開發(fā)者,會討論 Node 知識,互相學習」!3.也可添加微信【ikoala520】,一起成長。
“在看轉(zhuǎn)發(fā)”是最大的支持
