這次徹底搞懂Promise(手寫源碼多注釋篇)
前言
promise 是 js 里面非常重要的一部分,搞懂了 promise 才能更好的去理解 async,await 和 generator。但是往往很多時候就是不理解 promise 的機制,所以這次通過一步步實現(xiàn)一個 promise 來加深自己的印象,提高自己的思維。
大概的架子
通過我們經常寫的 promise 語法,我們可以先寫一個大概的架子出來,promise 接受回調,并且調用,自身帶有三種狀態(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ù)里面的第一個回調函數(shù)的集合
- self.onRejectedArr = []; // then函數(shù)里面的第二個回調函數(shù)的集合
- const resolve = function (value) {
- // 加這個判斷是為了表示,只有在pendding狀態(tài)下才會去執(zhí)行
- // 狀態(tài)已經變成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)的, 我們知道 調用 resolve(x), x 的值有好幾種情況,如下
- 如果 x 是 Promise 實例本身,則拋出錯誤
- 如果 x 是一個 Promise 對象,那么 then 函數(shù)的執(zhí)行取決這個 x 的狀態(tài),如果 x 也調用 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ù)
思考
- 網上實現(xiàn)的大部分 resolve 都是我上面的代碼,但是根據(jù)規(guī)范,resolve 函數(shù)里面應該是要判斷上面幾點的,所以我們上面寫的代碼是有誤的
- 還有一個問題是,我們需要在 resolve 函數(shù)里面判斷 x 是不是實例的本身,但是正常的 resolve 函數(shù)我們經常是傳入一個參數(shù),所以中間肯定是有一個中間函數(shù)的,看下面的代碼
- const PromiseCopy = function (fn) {
- this.info = {
- status: "pending",
- value: "",
- };
- const self = this;
- self.onFulfilledArr = []; // then函數(shù)里面的第一個回調函數(shù)的集合
- self.onRejectedArr = []; // then函數(shù)里面的第二個回調函數(shù)的集合
- // _resolve 是我們經常調用的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我們已經拿出來了then = value.then,直接調用then(),this就指向的window
- // 為什么后面還需要綁定兩個函數(shù)了
- // 根據(jù)原生的Promise可知,thenable中的then函數(shù)可以接受兩個函數(shù)resolve,reject
- // 只有手動調用了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已經不是pendding,我們給了他也沒有用,所以需要直接調用
- 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)已經變成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調用的是_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í)行的回調,一個是 onRejected 后執(zhí)行的回調。現(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ù)中的兩個回調函數(shù)只要有一個報錯,那么直接調用 promise2.then 函數(shù)中的錯誤回調
- 如果 promise1.then 的第一個回調不是函數(shù),并且 promise1 調用的是 resolve,那么 promise2.then 的第一個回調參數(shù)是 promise1 中 resolve 函數(shù)的拋出值
- 同理,如果 promise1.then 第二個回調不是函數(shù),并且 promise1 調用的是 reject,那么 promise2.then 中的錯誤回調就會執(zhí)行
思考
如果像上面這么說的話,這個新拋出來的 promise 何時調用這個 resolve 或者 reject 是一個關鍵, 并且這個拋出的 promise 的執(zhí)行還得看 onFulfilled 和 onRejected 返回值,這一點當時寫 promise 的時候想了很久,不知道如何組織,后來實在想不出來,看了下網上很多文章,發(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 newnewPromise = new PromiseCopy((resolve, reject) => {
- if (self.info.status === "onFulfilled") {
- setTimeout(() => {
- try {
- // 如果onFulfilled不是一個函數(shù)resolve--self.info.value
- let value = self.info.value;
- // 這個注釋不要,留著只是為了記錄當時的思路
- // 這個加判斷是為了防止then函數(shù)逇回調不是一個函數(shù),,是一個字符串
- // if (typeof onFulfilled === "function") {
- // value = onFulfilled(value);
- // }
- value = onFulfilled(value);
- // 這里要做一個[[Resolve]](promise2, x)處理了
- // 因為resolve里面直接做了,所以直接調用,和網上的一些實現(xiàn)有點不一樣
- // 他們是提取了一個resolvePromise函數(shù)調用,我是直接調用了resolve
- resolve(value);
- } catch (e) {
- reject(e);
- }
- });
- }
- // 注意這里根據(jù)上面可知onFulfilled,onRejected拋出的值都要經過[[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)已經完成了,下面是測試結果
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 回調,并且結果順序和數(shù)組的一致
- 如果其中一個 promise 發(fā)生了 reject 那么就會返回這個值
- PromiseCopy.all = function (data) {
- let count = 0; // 記錄調用次數(shù)
- let total = data.length;
- let result = [];
- return new PromiseCopy((resolve, reject) => {
- for (let i = 0; i < total; 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 < total; 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 newnewPromise = 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 跑不過
- PromiseCopyPromiseCopy.defer = PromiseCopy.deferred = function () {
- let dfd = {};
- dfd.promise = new PromiseCopy((resolve, reject) => {
- dfd.resolve = resolve;
- dfd.reject = reject;
- });
- return dfd;
- };
源碼
promise 源碼已經上傳到個人 github ( https://github.com/yizhengfeng-jj/promise 歡迎 star)