如何從無到有實現Promise(下)

溫故知新

上一篇《如何從無到有實現Promise(上)》中我們已經實現了一個看似可以正常工作的簡易版 Promise 凯旭,不要認為這樣就結束了评疗,其實好戲才剛剛開始。
本篇我們繼續(xù)改造和豐富這個 Promise,讓它可以適用更復雜的場景。

本文篇幅較長旁壮,又有大量的代碼片段,可以邊記錄筆記邊閱讀谐檀,對照著看更容以理解。


鏈式調用“有點東西”

眾所周知 Promisethen 方法是支持鏈式調用的裁奇,如下所示:

  • 鏈式調用場景1
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });

      promise
        .then((data) => {
          console.log(data);
          return "second then";
        })
        .then((data) => {
          console.log(data);
        });

輸出結果應該為桐猬,先打印 first then ,兩秒后打印 second then刽肠。

但是我們目前實現的 Promise 是不支持的溃肪,那該如何實現 Promise 實例的 then 方法支持鏈式調用這個行為呢免胃?

Promise 實例的 then 方法內的 onfulfilledonrejected 函數中惫撰,是支持再次返回一個 Promise 實例的羔沙,也支持返回一個普通值(非 Promise 實例);并且返回的這個 Promise 實例或者這個普通值將會傳給下一個 then 方法里 onfulfilled 厨钻、onrejected 函數中扼雏,如此, then 方法就支持了鏈式調用夯膀。

如上總結诗充,想要支持 then 的鏈式調用,就要每一個 then 方法的 onfulfilled 函數和 onrejected 函數都要返回一個 Promise 實例诱建。
我們先支持鏈式調用 then 方法時返回一個普通的值的情況蝴蜓,改造 then 方法:

      Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
        let promise2;

        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            this.onFulfilledArray.push(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

結合我們上面的分析,代碼不難理解俺猿,當調用 Promise 實例 then 方法的時候茎匠,應該再次返回一個 Promise 實例,promise2 就將作為 then 方法的返回值押袍。

這個 promise2 是什么時候被 resolve 或者 reject 的呢?

  • 當判斷分支是 status === "fulfilled"或者 status === "rejected"的時候诵冒!

這個 promise2 在執(zhí)行完 then 方法時就被 resolve 或者 reject 了。

  • 當判斷分支為 status === "pending"時情況較為復雜伯病!

返回的 promise2 實例的 resolvereject 是放到 onFulfilledArrayonRejectedArray數組中的造烁。
當異步執(zhí)行完成后,依次執(zhí)行 onFulfilledArrayonRejectedArray 數組內的函數時才執(zhí)行了 promise2resolve午笛、reject惭蟋。
那么在 onFulfilledArrayonRejectedArray 數組中的函數內應該 resolve 或是 rejectpromise2,并且傳入的參數就是 onfulfilled 或者 onrejected 的執(zhí)行結果药磺。

這樣 then 方法支持鏈式調用告组,并且支持返回一個普通值的情況。
再來整體看下目前為止的完整代碼:

      function Promise(excutor) {
        this.status = "pending";
        this.resolveVal = null;
        this.rejectVal = null;
        this.onFulfilledFuncArray = [];
        this.onRejectedFuncArray = [];

        const resolve = (value) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.resolveVal = value;
              this.status = "fulfilled";
              this.onFulfilledFuncArray.forEach((fn) => {
                fn(this.resolveVal);
              });
            }
          }, 0);
        };

        const reject = (error) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.rejectVal = error;
              this.status = "rejected";
              this.onRejectedFuncArray.forEach((fn) => {
                fn(this.rejectVal);
              });
            }
          }, 0);
        };

        try {
          excutor(resolve, reject);
        } catch (error) {
          reject(error);
        }
      }

      Promise.prototype.then = function (
        onfulfilled = Function.prototype,
        onrejected = Function.prototype
      ) {
        let promise2;

        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            this.onFulfilledArray.push(() => {
              try {
                let result = onfulfilled(this.resolveVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolve(result);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

上面也說了癌佩,then 方法也是支持顯式返回一個 Promise 實例的情況木缝,如下。

  • 鏈式調用場景2
      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });
      promise
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve("second then");
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
        });

第一種情況是 then 方法的 onFulfilledonrejected 函數返回的是一個普通值围辙,與之不同的是這里我們要支持 onFulfilledonrejected 函數返回的是一個 Promise 實例我碟。

      let result = onfulfilled(this.resolveVal);
      let result = onrejected(this.rejectVal);

場景1中,上面兩行代碼得到的結果都是一個普通值姚建,也就是說 result 就是一個普通值矫俺,現在要做的是讓 result 可以為普通值,也可以為 Promise 實例。所以我們就不能直接對 result 進行 resolve(result) 的操作厘托。

綜上所述友雳,我們抽象出 resolvePromise 方法進行統(tǒng)一處理,替換原 resolve 方法铅匹。
接下來就要完成這個 resolvePromise 函數:
首先我們定義方法參數:

  • promise2:返回的 Promise 實例
  • result:onfulfilled 或者 onrejected 函數的返回值
  • resolve: promise2resolve 方法
  • reject: promise2reject 方法

方法實現為:

      const resolvePromise = (promise2, result, resolve, reject) => {
        if ((typeof result === "function" || typeof result === "object") && result !== null) {
          try {
            if (typeof result.then === "function") {
              result.then.call(
                result,
                function (value) {
                  return resolvePromise(promise2, value, resolve, reject);
                },
                function (error) {
                  return reject(error);
                }
              );
            } else {
              resolve(result);
            }
          } catch (e) {
            return reject(e);
          }
        } else {
          resolve(result);
        }
      };

看不懂不要急押赊,此處是本文最大的難點了,也是鏈式調用的核心所在包斑,接下來將目前的代碼整合在一起流礁,然后仔細分析下 resolvePromise 究竟做了什么。
附加注釋的完整代碼如下:

      function Promise(executor) {
        this.status = "pending";
        this.resolveVal = null;
        this.rejectVal = null;
        this.onFulfilledArray = [];
        this.onRejectedArray = [];

        const resolve = (value) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.resolveVal = value;
              this.status = "fulfilled";

              this.onFulfilledArray.forEach((fn) => {
                fn(this.resolveVal);
              });
            }
          });
        };

        const reject = (error) => {
          setTimeout(() => {
            if (this.status === "pending") {
              this.rejectVal = error;
              this.status = "rejected";

              this.onRejectedArray.forEach((fn) => {
                fn(this.rejectVal);
              });
            }
          });
        };

        try {
          executor(resolve, reject);
        } catch (e) {
          reject(e);
        }
      }

      // 參數1:promise2實例  參數2:onfulfilled 和 onrejected 執(zhí)行結果   參數3舰始、4:promise2 的 resolve 以及 reject
      const resolvePromise = (promise2, result, resolve, reject) => {
        // 如果返回數據可能是 Promise 類型(再進行 .then 判斷后才可真正確認崇棠,在下面處理)
        if (
          (typeof result === "function" || typeof result === "object") &&
          result !== null
        ) {
          try {
            // 通過 then 方法判斷可以確定是否是 Promise 類型
            if (typeof result.then === "function") {
              // 執(zhí)行 then 方法 , 參數分別為 onfulfilled 和 onrejected
              result.then.call(
                result,
                function (value) {
                  // onfulfilled 函數
                  // 當 result 是 Promise 類型時丸卷,遞歸調用 resolvePromise 函數枕稀,直到 result 不再是 Promise 類型,執(zhí)行 promise2 (當前then的返回promise)的 resolve谜嫉。
                  return resolvePromise(promise2, value, resolve, reject);
                },
                function (error) {
                  // onrejected 函數
                  return reject(error);
                }
              );
            } else {
              // result 不是Promise類型萎坷,直接 resolve promise2
              resolve(result);
            }
          } catch (e) {
            return reject(e);
          }
        } else {
          // result 不是Promise類型,直接 resolve promise2
          resolve(result);
        }
      };

      Promise.prototype.then = function (onfulfilled = Function.prototype, onrejected = Function.prototype) {
        // Promise 需要支持鏈式調用沐兰,所以 then 方法也要返回一個 Promise 實例
        let promise2;

        // 因為 resolve 函數內邏輯是異步執(zhí)行的哆档,所以只有 then 方法被異步調用,才會進入這個分支住闯,在執(zhí)行 then 方法的時候瓜浸,resolve 操作已經完成,狀態(tài)已經變更
        if (this.status === "fulfilled") {
          return (promise2 = new Promise((resolve, reject) => {
            // 返回的 promise2 中的代碼要異步執(zhí)行
            setTimeout(() => {
              try {
                // 執(zhí)行 onfulfilled 函數比原,得到返回結果插佛。
                let result = onfulfilled(this.resolveVal);
                // 得到結果 result 可能是普通值,可能依然是 Promise 實例量窘,通過 resolvePromise 進行處理
                // 并且在 resolvePromise 函數中進行 promise2 的 resolve 或者 reject 操作
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        // 與上面同理
        if (this.status === "rejected") {
          return (promise2 = new Promise((resolve, reject) => {
            setTimeout(() => {
              try {
                let result = onrejected(this.rejectVal);
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }

        // 因為 resolve 函數內邏輯是異步執(zhí)行的雇寇,因此當 then 方法被同步調用的時候,resolve 內的邏輯還未執(zhí)行蚌铜,狀態(tài)依然是 pending
        // 此時需要保存調用 then 方法時傳入的 onfulfilled 和 onrejected 函數锨侯,在 resolve 執(zhí)行時再取出執(zhí)行
        if (this.status === "pending") {
          return (promise2 = new Promise((resolve, reject) => {
            // 因為同一個 Promise 實例可能有多個 then 方法,所以將所有 then 方法內的 onfulfilled 函數進行保存冬殃,需要時依次執(zhí)行
            this.onFulfilledArray.push((value) => {
              try {
                // 執(zhí)行 onfulfilled 函數囚痴,得到返回結果。
                let result = onfulfilled(value);
                // 得到結果 result 可能是普通值审葬,可能依然是 Promise 實例渡讼,通過 resolvePromise 進行處理
                // 并且在 resolvePromise 函數中進行 promise2 的 resolve 或者 reject 操作
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });

            this.onRejectedArray.push((error) => {
              try {
                let result = onrejected(error);
                resolvePromise(promise2, result, resolve, reject);
              } catch (e) {
                reject(e);
              }
            });
          }));
        }
      };

這應該是兩篇文章到現在最接近最終實現并且注釋最詳細的一次了骂束。不過依然不能改變它非常難以理解的事實,理解的關鍵在于弄清楚當 resultPromise 類型時成箫,和 promise2 的關系,以及當 resultPromise 類型時遞歸調用 resolvePromise 函數的目的旨枯。

沒有什么捷徑蹬昌,只能多看多寫多思考吧,寫出測試代碼攀隔,然后順著執(zhí)行順序去跟蹤代碼皂贩。
我想出了這個流程圖盡可能地幫助大家理解。
先看測試代碼:

      const promise = new Promise((resolve, reject) => {
        setTimeout(() => {
          resolve("first then");
        }, 2000);
      });

      promise
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              reject(`second then`);
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
          return new Promise((resolve, reject) => {
            setTimeout(() => {
              resolve(`last then`);
            }, 2000);
          });
        })
        .then((data) => {
          console.log(data);
        });

2秒后輸出 'first then' 再過2秒后輸出 'second then' 再過2秒后輸出 'last then'

在這里插入圖片描述

回過頭來再看昆汹,其實 resolvePromise 方法的作用非常明確明刷,當 onfulfilled 函數返回的數據(result)為普通值的話,還是像場景1一樣直接 resolve promise2 處理即可满粗,但是如果當這個 resultPromise 類型時辈末,就要在 result.then.onfulfilled 中去遞歸調用 resolvePromise ,當再進去 resolvePromise 的時候映皆,此時新的 result 參數如果是普通值了挤聘,就 resolve promise2 ,并將結果作為參數返回即可捅彻。

到這如果你都可以理解组去,可以說是基本掌握了 Promise 的除靜態(tài)方法外的全部基礎內容。為了簡化代碼步淹,更容易理解从隆,一些容錯機制沒有添加,并不影響整體思路的學習缭裆。


靜態(tài)方法键闺,不說你也會

靜態(tài)方法其實非常簡單,不說你也應該會幼驶,但是我還是簡單說下吧[捂臉]艾杏。
關于 Promise 的靜態(tài)方法如 Promise.resolvePromise.reject盅藻、 Promise.all 等等购桑,就不再一一實現了,只選擇平時用的比較多的 Promise.all 實現以下氏淑。

  • Promise.all 的實現

Promise.all(iterable) 方法返回一個 Promise 實例勃蜘,此實例在 iterable 參數內所有的 promise 都“完成(resolved)”或參數中不包含 promise 時回調完成(resolve);
如果參數中 promise 有一個失敿俨小(rejected)缭贡,此實例回調失斅谩(reject),失敗原因的是第一個失敗 promise 的結果阳惹。

看下具體使用谍失。
場景1:

      const promise1 = new Promise((resolve, reject) => {
        resolve("p1");
      });

      const promise2 = new Promise((resolve, reject) => {
        resolve("p2");
      });

      Promise.all([promise1, promise2]).then((data) => {
        console.log(data);
      });

打印出 ["p1", "p2"]

場景2:

      const promise1 = new Promise((resolve, reject) => {
        resolve("p1");
      });

      const promise2 = new Promise((resolve, reject) => {
        reject("p2 失敗");
      });

      Promise.all([promise1, promise2])
        .then((data) => {
          console.log(data);
        })
        .catch((error) => {
          console.log(error);
        });

打印出 p2 失敗

對照著使用,來實現這個 Promise.all

      Promise.all = function (promiseArray) {
        if (!Array.isArray(promiseArray)) {
          throw new TypeError("promiseArray should be array!");
        }

        // 在外層包裹一個 Promise 莹汤,如果內部有一個 Promise 執(zhí)行不成功快鱼,就執(zhí)行最外層 Promise 的 reject
        return new Promise((resolve, reject) => {
          try {
            let resultArray = [];

            for (let i = 0; i < promiseArray.length; i++) {
              promiseArray[i].then((data) => {
                // 記錄 resolve 的值
                resultArray.push(data);

                // 全部完成后 執(zhí)行外出 Promise 的 resolve 將存放每一個成功值得數組返回即可
                if (resultArray.length === promiseArray.length) {
                  resolve(resultArray);
                }
              }, reject);
            }
          } catch (e) {
            reject(e);
          }
        });
      };

實現起來非常容易,其他幾個靜態(tài)方法思路也大同小異纲岭,有時間可以自行補充抹竹。


結束啦!

總結 Promise 的兩篇筆記終于結束啦止潮,實現 Promise 并不是目的窃判,況且也不是完全按照規(guī)范去實現的,目的是學習思路喇闸,深層次得理解原理袄琳,達到融會貫通,這樣以后不論是自己的代碼設計參考它的實現原理還是在 Promise 的使用上遇到問題仅偎,解決起來都會得心應手跨蟹。

這部分內容還是比較難理解的,整理筆記得時候也會有很多地方需要靜下心來梳理思路橘沥。剛接觸一頭霧水也再正常不過了窗轩,不要灰心,多看多寫多想座咆,每看一次都會有不同的收獲痢艺。

?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市介陶,隨后出現的幾起案子堤舒,更是在濱河造成了極大的恐慌,老刑警劉巖哺呜,帶你破解...
    沈念sama閱讀 211,265評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件舌缤,死亡現場離奇詭異,居然都是意外死亡某残,警方通過查閱死者的電腦和手機国撵,發(fā)現死者居然都...
    沈念sama閱讀 90,078評論 2 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來玻墅,“玉大人介牙,你說我怎么就攤上這事“南幔” “怎么了环础?”我有些...
    開封第一講書人閱讀 156,852評論 0 347
  • 文/不壞的土叔 我叫張陵囚似,是天一觀的道長。 經常有香客問我线得,道長饶唤,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,408評論 1 283
  • 正文 為了忘掉前任贯钩,我火速辦了婚禮搬素,結果婚禮上,老公的妹妹穿的比我還像新娘魏保。我一直安慰自己,他們只是感情好摸屠,可當我...
    茶點故事閱讀 65,445評論 5 384
  • 文/花漫 我一把揭開白布谓罗。 她就那樣靜靜地躺著,像睡著了一般季二。 火紅的嫁衣襯著肌膚如雪檩咱。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,772評論 1 290
  • 那天胯舷,我揣著相機與錄音刻蚯,去河邊找鬼。 笑死桑嘶,一個胖子當著我的面吹牛炊汹,可吹牛的內容都是我干的。 我是一名探鬼主播逃顶,決...
    沈念sama閱讀 38,921評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼讨便,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了以政?” 一聲冷哼從身側響起霸褒,我...
    開封第一講書人閱讀 37,688評論 0 266
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎盈蛮,沒想到半個月后废菱,有當地人在樹林里發(fā)現了一具尸體,經...
    沈念sama閱讀 44,130評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡抖誉,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,467評論 2 325
  • 正文 我和宋清朗相戀三年殊轴,在試婚紗的時候發(fā)現自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片寸五。...
    茶點故事閱讀 38,617評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡梳凛,死狀恐怖,靈堂內的尸體忽然破棺而出梳杏,到底是詐尸還是另有隱情韧拒,我是刑警寧澤淹接,帶...
    沈念sama閱讀 34,276評論 4 329
  • 正文 年R本政府宣布,位于F島的核電站叛溢,受9級特大地震影響塑悼,放射性物質發(fā)生泄漏。R本人自食惡果不足惜楷掉,卻給世界環(huán)境...
    茶點故事閱讀 39,882評論 3 312
  • 文/蒙蒙 一厢蒜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧烹植,春花似錦斑鸦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,740評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至墩虹,卻和暖如春嘱巾,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背诫钓。 一陣腳步聲響...
    開封第一講書人閱讀 31,967評論 1 265
  • 我被黑心中介騙來泰國打工旬昭, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人菌湃。 一個月前我還...
    沈念sama閱讀 46,315評論 2 360
  • 正文 我出身青樓问拘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親慢味。 傳聞我的和親對象是個殘疾皇子场梆,可洞房花燭夜當晚...
    茶點故事閱讀 43,486評論 2 348

推薦閱讀更多精彩內容