日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

「3.4w字」超保姆级教程带你实现Promise的核心功能

發布時間:2023/12/4 编程问答 30 豆豆
生活随笔 收集整理的這篇文章主要介紹了 「3.4w字」超保姆级教程带你实现Promise的核心功能 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

保姆級詳解promise的核心功能

  • 📚序言
  • 📋文章內容搶先看
  • 📰一、js的同步模式和異步模式
    • 1. 單線程💡
    • 2. 同步模式💡
      • (1)定義
      • (2)圖例
    • 3. 異步模式💡
      • (1)舉例
      • (2)定義
      • (3)js如何實現異步
      • (4)event loop過程
    • 4. 回調函數💡
  • 📃二、Promise異步方案
    • 1. Promise的三種狀態📂
      • (1)Promise的三種狀態
      • (2)狀態解釋
    • 2. 三種狀態的變化和表現📂
      • (1)狀態的變化
      • (2)狀態的表現
    • 3. Promise的使用案例📂
    • 4. then和catch對狀態的影響📂
    • 5. Promise的并行執行📂
      • (1)Promise.all
      • (2)Promise.race
    • 6. 兩個有用的附加方法📂
      • (1)done()
      • (2)finally()
  • 📑三、實現Promise的核心功能
    • 1. 基礎核心功能實現🏷?
      • (1)碎碎念
      • (2)Promise基礎功能的分析
      • (3)Promise基礎功能的實現
      • (4)thenable功能的分析
      • (5)thenable功能的實現
    • 2. 添加異步邏輯功能實現🏷?
      • (1)then中添加異步邏輯功能的分析
      • (2)then中添加異步邏輯功能的實現
    • 3. 實現then方法的多次調用🏷?
      • (1)多次調用then方法的功能分析
      • (2)多次調用then方法的功能實現
    • 4. 實現then方法的鏈式調用🏷?
      • (1)then方法鏈式調用的功能分析
      • (2)then方法鏈式調用的功能實現
      • (3)鏈式調用的自我檢測
    • 5. promise的錯誤處理🏷?
      • (1)錯誤處理場景
      • (2)錯誤處理功能實現
    • 6. 實現then方法的參數可選🏷?
      • (1)參數可選實現思路
      • (2)參數可選功能實現
    • 7. 實現Promise.all🏷?
      • (1)Promise.all功能分析
      • (2)Promise.all功能實現
    • 8. 實現Promise.resolve🏷?
      • (1)Promise.resolve功能分析
      • (2)Promise.resolve功能實現
  • 📝四、結束語
  • 🐣彩蛋 One More Thing
    • (:課代表記錄
    • (:參考資料
    • (:番外篇

📚序言

眾所周知, promise 是前端面試中雷打不動的面試題了,面試官都很愛考。周一之前也是知識比較浮于表面,在一些面經上看到了 promise 的實現方式,就只停留在那個層面上。但實際上我發現,如果沒有深入其原理去理解,面試官稍微變個法子來考,這道題很容易就把我給問倒了。所以呀,還是老老實實從頭到尾研究一遍,這樣等遇到了,不管怎么考,萬宗不變其一,把原理理解了,就沒有那么容易被問倒了。

下面開始進入本文的講解~🏷?

📋文章內容搶先看

📰一、js的同步模式和異步模式

1. 單線程💡

大家都知道, js 的設計是基于單線程進行開發的,它原先的目的在于只參與瀏覽器中DOM節點的操作。

而對于單線程來說,其意味著只能執行一個任務,且所有的任務都會按照隊列的模式進行排隊。

所以,單線程的缺點就在于,當 js 運行的時候, html 是不會進行渲染的。因此,如果一個任務特別耗時,那么將會很容易造成頁面阻塞的局面。

為了解決這個問題, js 提出了同步模式異步模式的解決方案。

2. 同步模式💡

(1)定義

所謂同步模式,指的就是函數中的調用堆棧,按照代碼實現的順序,一步步進行。

(2)圖例

接下來我們來用一段代碼,演示 js 中函數調用堆棧的執行情況。具體代碼如下:

const func1 = () => {func2();console.log(3); }const func2 = () => {func3();console.log(4); }const func3 = () => {console.log(5); }func1(); //5 4 3

看到這里,相信很多小伙伴已經在構思其具體的執行順序。下面用一張圖來展示執行效果:

對于這個數據結構來說,它遵循后進先出的原則。因此,當 func1 , func2 , func3 依次放進調用棧后, 遵循后進先出原則 ,那么 func3 函數的內容會先被執行,之后是 func2 ,最后是 func1 。

因此,對于 js 的同步模式來說,就是類似于上述的函數調用堆棧。

3. 異步模式💡

(1)舉例

當程序遇到網絡請求或定時任務等問題時,這個時候會有一個等待時間。

假設一個定時器設置 10s ,如果放在同步任務里,同步任務會阻塞代碼執行,我們會等待 10s 后才能看到我們想要的結果。1個定時器的等待時間可能還好,如果這個時候是100個定時器呢?我們總不能等待著 1000s 的時間就為了看到我們想要的結果吧,這幾乎不太現實。

那么這個時候就需要異步,通過異步來讓程序不阻塞代碼執行靈活執行程序

(2)定義

對于同步模式來說,它只能自上而下地一行一行執行,一行一行進行解析。那與同步模式不同的是,異步模式是按照我們想要的結果進行輸出,不會像同步模式一樣產生阻塞,以達到讓程序可控的效果。

(3)js如何實現異步

相對于同步模式來說,異步模式的結構更為復雜。除了調用棧之外, 它還有消息隊列事件循環這兩個額外的機制。所謂事件循環,也稱為 event loop 或事件輪詢。因為 js 是單線程的,且異步需要基于回調來實現,所以, event loop 就是異步回調的實現原理。

JS在程序中的執行遵循以下規則:

  • 從前到后,一行一行執行;
  • 如果某一行執行報錯,則停止下面代碼的執行;
  • 先把同步代碼執行完,再執行異步。

一起來看一個實例:

console.log('Hi');setTimeout(function cb1(){console.log('cb1'); //cb1 即callback回調函數 }, 5000);console.log('Bye');//打印順序: //Hi //Bye //cb1

從上例代碼中可以看到, JS 是先執行同步代碼,所以先打印 Hi 和 Bye ,之后執行異步代碼,打印出 cb1 。

以此代碼為例,下面開始講解 event loop 的過程。

(4)event loop過程

對于上面這段代碼,執行過程如下圖所示:

從上圖中可以分析出這段代碼的運行軌跡。首先 console.log('Hi') 是同步代碼,直接執行并打印出 Hi 。接下來繼續執行定時器 setTimeout ,定時器是異步代碼,所以這個時候瀏覽器會將它交給 Web APIs 來處理這件事情,因此先把它放到 Web APIs 中,之后繼續執行 console.log('Bye') , console.log('Bye') 是同步代碼,在調用堆棧 Call Stack 中執行,打印出 Bye 。

到這里,調用堆棧 Call Stack 里面的內容全部執行完畢,當調用堆棧的內容為空時,瀏覽器就會開始去 消息隊列 Callback Queue 尋找下一個任務,此時消息隊列就會去 Web API 里面尋找任務,遵循先進先出原則,找到了定時器,且定時器里面是回調函數 cb1 ,于是把回調函數 cb1 傳入任務隊列中,此時 Web API 也空了,任務隊列里面的任務就會傳入到調用堆棧里Call Stack 里執行,最終打印出 cb1 。

4. 回調函數💡

早期我們在解決異步問題的時候,基本上都是使用callback回調函數的形式 來調用的。形式如下:

//獲取第一份數據 $.get(url1, (data1) => {console.log(data1);//獲取第二份數據$.get(url2, (data2) => {console.log(data2);//獲取第三份數據$.get(url3, (data3) => {console.log(data3);//還可以獲取更多數據});}); });

從上述代碼中可以看到,早期在調用數據的時候,都是一層套一層, callback 調用 callback ,仿佛深陷調用地獄一樣,數據也被調用的非常亂七八糟的。所以,因為 callback 對開發如此不友好,也就有了后來的 promise 產生。

promise 由 CommonJS 社區最早提出,之后在2015年的時候, ES6 將其寫進語言標準中,統一了它的用法,原生提供了 Promise 對象。 promise 的出現,告別了回調地獄時代,解決了回調地獄 callback hell 的問題。

那下面我們就來看看 Promise 的各種神奇用法~

📃二、Promise異步方案

1. Promise的三種狀態📂

(1)Promise的三種狀態

狀態含義
pending等待狀態,即在過程中,還沒有結果。比如正在網絡請求,或定時器沒有到時間。
fulfilled滿足狀態,即事件已經解決了,并且成功了;當我們主動回調了 fulfilled 時,就處于該狀態,并且會回調 then 函數。
rejected拒絕狀態,即事件已經被拒絕了,也就是失敗了;當我們主動回調了 reject 時,就處于該狀態,并且會回調 catch 函數。

(2)狀態解釋

對于 Promise 來說,它是一個對象,用來表示一個異步任務在執行結束之后返回的結果,它有 3 種狀態: pending , fulfilled , rejected 。其執行流程如下:

如果一個異步任務處于 pending 狀態時,那么表示這個 promise 中的異步函數還未執行完畢,此時處于等待狀態。相反,如果 promise 中的異步函數執行完畢之后,那么它只會走向兩個結果:

  • fulfilled ,表示成功
  • rejected ,表示失敗

一旦最終狀態從 pending 變化為 fulfilled 或者 rejected 后,狀態就再也不可逆

所以,總結來講,Promise 對象有以下兩個特點:

  • promise 對象的狀態不受外界影響,一旦狀態被喚起之后,函數就交由 web API 去處理,這個時候在函數主體中再執行任何操作都是沒有用的
  • 只會出現 pending → fulfilled,或者 pending → rejected 狀態,即要么成功要么失敗。即使再對 promise 對象添加回調函數,也只會得到同樣的結果,即它的狀態都不會再發生被改變

2. 三種狀態的變化和表現📂

(1)狀態的變化

promise 主要有以上三種狀態, pending 、 fulfilled 和 rejected 。當返回一個 pending 狀態的 promise 時,不會觸發 then 和 catch 。當返回一個 fulfilled 狀態時,會觸發 then 回調函數。當返回一個 rejected 狀態時,會觸發 catch 回調函數。那在這幾個狀態之間,他們是怎么變化的呢?

1)演示1

先來看一段代碼:

const p1 = new Promise((resolved, rejected) => {});console.log('p1', p1); //pending

在以上的這段代碼中,控制臺打印結果如下:

在這段代碼中, p1 函數里面沒有內容可以執行,所以一直在等待狀態,因此是 pending 。

2)演示2

const p2 = new Promise((resolved, rejected) => {setTimeout(() => {resolved();}); });console.log('p2', p2); //pending 一開始打印時 setTimeout(() => console.log('p2-setTimeout', p2)); //fulfilled

在以上的這段代碼中,控制臺打印結果如下:

在這段代碼中, p2 一開始打印的是 pending 狀態,因為它沒有執行到 setTimeout 里面。等到后續執行 setTimeout 時,才會觸發到 resolved 函數,觸發后返回一個 fulfilled 狀態 promise 。

3)演示3

const p3 = new Promise((resolved, rejected) => {setTimeout(() => {rejected();}); });console.log('p3', p3); setTimeout(() => console.log('p3-setTimeout', p3)); //rejected

在以上的這段代碼中,控制臺打印結果如下。

在這段代碼中, p3 一開始打印的是 pending 狀態,因為它沒有執行到 setTimeout 里面。等到后續執行 setTimeout 時,同樣地,會觸發到 rejected 函數,觸發后返回一個 rejected 狀態的 promise 。

看完 promise 狀態的變化后,相信大家對 promise 的三種狀態分別在什么時候觸發會有一定的了解。那么我們接下來繼續看 promise 狀態的表現。

(2)狀態的表現

  • pending 狀態,不會觸發 then 和 catch 。
  • fulfilled 狀態,會觸發后續的 then 回調函數。
  • rejected 狀態,會觸發后續的 catch 回調函數。

我們來演示一下。

1)演示1

const p1 = Promise.resolve(100); //fulfilled console.log('p1', p1); p1.then(data => {console.log('data', data); }).catch(err => {console.error('err', err); });

在以上的這段代碼中,控制臺打印結果如下:

在這段代碼中, p1 調用 promise 中的 resolved 回調函數,此時執行時, p1 屬于 fulfilled 狀態, fulfilled 狀態下,只會觸發 .then 回調函數,不會觸發 .catch ,所以最終打印出 data 100 。

2)演示2

const p2 = Promise.reject('404'); //rejected console.log('p2', p2); p2.then(data => {console.log('data2', data); }).catch(err => {console.log('err2', err); })

在以上的這段代碼中,控制臺打印結果如下:

在這段代碼中, p2 調用 promise 中的 reject 回調函數,此時執行時, p1 屬于 reject 狀態, reject 狀態下,只會觸發 .catch 回調函數,不會觸發 .then ,所以最終打印出 err2 404 。

3. Promise的使用案例📂

對三種狀態有了基礎了解之后,我們用一個案例來精進對 Promise 的使用。現在,我們想要實現的功能是,通過 fs 模塊,異步地調用本地的文件。如果文件存在,那么在控制臺上輸出文件的內容;如果文件不存在,則將拋出異常。實現代碼如下:

const fs = require('fs');const readFile = (filename) => {// 返回一個 promise 實例,以供 then 調用const promise = new Promise(function(resolve, reject){// 使用 readFile 去異步地讀取文件,異步調用也是 promise 函數的意義// 注意:下面這個函數的邏輯是錯誤優先,也就是先err,再datafs.readFile(filename, (err, data) => {// 如果文件讀取失敗,就調取 reject ,并拋出異常if(err){reject(err);}else{// 如果成功,就調取 resolve ,并返回調用成功的數據resolve(data);}});});return promise; }// 測試代碼 // 文件存在邏輯 const existedFile = readFile('./test.txt'); existedFile.then((data) => {// Buffer.from()方法用于創建包含指定字符串,數組或緩沖區的新緩沖區。// Buffer.from(data).toString()讀出文件里面的內容。文件里面記得寫內容!!console.log('content: ', Buffer.from(data).toString());},(error) => {console.log(error);} )// 文件不存在邏輯 const failFile = readFile('./fail.txt'); failFile.then((data) => {console.log(Buffer.from(data).toString());},(err) => {console.log(err);} );

最終控制臺的打印結果如下:

[Error: ENOENT: no such file or directory, open 'C:\\promise\\fail.txt'] {errno: -4058,code: 'ENOENT',syscall: 'open',path: 'C:\\promise\\fail.txt' } content: 這是一個測試文件!

大家可以看到,當 ./test.txt 文件存在時,那么 existedFile 會去調用后續的 .then 回調函數,因此最終返回調用成功的結果。注意,這是一個測試文件! 這行字就是 test 文件里面的內容。

同時, ./fail.txt 文件不存在,因此 failFile 會調用后續的 .catch 文件,同時將異常拋出。

現在,大家應該對 promise 的使用有了一定的了解,下面我們繼續看 promise 中 then 和 catch 對狀態的影響。

4. then和catch對狀態的影響📂

  • then 正常返回 fulfilled ,里面有報錯則返回 rejected ;
  • catch 正常返回 fulfilled ,里面有報錯則返回 rejected 。

我們先來看第一條規則: then 正常返回 fulfilled ,里面有報錯則返回 rejected 。

1)演示1

const p1 = Promise.resolve().then(() => {return 100; }) console.log('p1', p1); //fulfilled狀態,會觸發后續的.then回調 p1.then(() => {console.log('123'); });

在以上的這段代碼中,控制臺打印結果如下。

在這段代碼中, p1 調用 promise 中的 resolve 回調函數,此時執行時, p1 正常返回 fulfilled , 不報錯,所以最終打印出 123 。

2)演示2

const p2 = Promise.resolve().then(() => {throw new Error('then error'); }); console.log('p2', p2); //rejected狀態,觸發后續.catch回調 p2.then(() => {console.log('456'); }).catch(err => {console.error('err404', err); });

在以上的這段代碼中,控制臺打印結果如下。

在這段代碼中, p2 調用 promise 中的 resolve 回調函數,此時執行時, p2 在執行過程中,拋出了一個 Error ,所以,里面有報錯則返回 rejected 狀態 , 所以最終打印出 err404 Error: then error 的結果。

我們再來看第二條規則: catch 正常返回 fulfilled ,里面有報錯則返回 rejected 。

1)演示1(需特別謹慎! !)

const p3 = Promise.reject('my error').catch(err => {console.error(err); }); console.log('p3', p3); //fulfilled狀態,注意!觸發后續.then回調 p3.then(() => {console.log(100); });

在以上的這段代碼中,控制臺打印結果如下。

在這段代碼中, p3 調用 promise 中的 rejected 回調函數,此時執行時, p3 在執行過程中,正常返回了一個 Error ,這個點需要特別謹慎!!這看起來似乎有點違背常理,但對于 promise 來說,不管時調用 resolved 還是 rejected ,只要是正常返回而沒有拋出異常,都是返回 fulfilled 狀態。所以,最終 p3 的狀態是 fulfilled 狀態,且因為是 fulfilled 狀態,之后還可以繼續調用 .then 函數。

2)演示2

const p4 = Promise.reject('my error').catch(err => {throw new Error('catch err'); }); console.log('p4', p4); //rejected狀態,觸發.catch回調函數 p4.then(() => {console.log(200); }).catch(() => {console.log('some err'); });

在以上的這段代碼中,控制臺打印結果如下。

在這段代碼中, p4 依然調用 promise 中的 reject 回調函數,此時執行時, p4 在執行過程中,拋出了一個 Error ,所以,里面有報錯則返回 rejected 狀態 , 此時 p4 的狀態為 rejected ,之后觸發后續的 .catch 回調函數。所以最終打印出 some err 的結果。

5. Promise的并行執行📂

(1)Promise.all

Promise.all 方法用于將多個 Promise 實例包裝成一個新的 Promise 實例。比如:

var p = Promise.all([p1, p2, p3]);

p的狀態由 p1 、 p2 、 p3 決定,分成兩種情況:

  • 只有 p1 、 p2 、 p3 的狀態都變為 fulfilled ,最終 p 的狀態才會變為 fulfilled 。此時 p1 、 p2 、 p3 的返回值組成一個數組,并返回給 p 回調函數。
  • 只要 p1 、 p2 、 p3 這三個參數中有任何一個被 rejected , 那么 p 的狀態就會變成 rejected 。此時第一個被 rejected 的實例的返回值將會返回給 p 的回調函數。

下面用一個實例來展示 Promise.all 的使用方式。具體代碼如下:

//生成一個Promise對象的數組 var promises = [4, 8, 16, 74, 25].map(function (id) {return getJSON('/post/' + id + ".json"); });Promise.all(promises).then(fucntion (posts) {// ... }).catch(function (reason) {// ... }}

大家可以看到,對于以上代碼來說, promises 是包含5個Promise實例的數組,只有這5個實例的狀態都變成 fulfilled ,或者其中有一個變為 rejected ,那么才會調用 Promise.all 方法后面的回調函數。


這里有一種值得注意的特殊情況是,如果作為參數的 Promise 實例自身定義了 catch 方法,那么它被 rejected 時并不會觸發 Promise.all() 的 catch 方法。這樣說可能比較抽象,我們用一個實例來展示一下,具體代碼如下:

const p1 = new Promise((resolve, reject) => {resolve('hello'); }).then(result => {return result; }).catch(e => {return e; });const p2 = new Promise((resolve, reject) => {throw new Error('報錯了'); }).then(result => {return result; }).catch(e => {return e; });Promise.all([p1, p2]).then(result => {console.log(result); }).catch(e => {console.log(e); })

在上面的代碼中, p1 會 resolve ,之后調用后續的 .then 回調函數。而 p2 會 reject ,因此之后會調用后續的 .catch 回調函數。注意,這里的 p2 有自己的 catch 方法,且該方法返回的時一個新的 Promise 實例,而 p2 實際上指向的就是這個實例。

所以呢,這個實例執行完 catch 方法后也會變成 resolved 。因此, 在 Promise.all() 這個方法中,其參數里面的兩個實例就都會 resolved ,所以之后會調用 then 方法指定的回調函數,而不會調用 catch 方法指定的回調函數。

(2)Promise.race

Promise.race 方法同樣是將多個 Promise 實例包裝成一個新的 Promise 實例。比如:

var p = Promise.race([p1, p2, p3]);

我們同樣用以上這段代碼來進行分析。與 Promise.all() 不同的是,只要 p1 、 p2 、 p3 中有一個實例率先改變狀態,那么** p 的狀態就會跟著改變**,且那個率先改變的 Promise 實例的返回值就會傳遞給 p 的回調函數。

所以呀,為什么它叫 race ? race ,顧名思義就是競賽的意思。在賽場上,第一名永遠只有一個。而我們可以把第一名視為第一個 resolve 狀態的 promise ,只要第一名出現了,那么結果就是第一名贏了,所以返回的值就是第一個為 resolve 的值。其他人再怎么賽跑都逃不過拿不到第一的現實。

6. 兩個有用的附加方法📂

ES6 中 Promise API 并沒有提供很多方法,但是我們可以自己來部署一些有用的方法。接下來,我們將來部署兩個不在 ES6 中但是卻很有用的方法。

(1)done()

無論 Promise 對象的回調鏈以 then 方法還是 catch 方法結尾,只要最后一個方法拋出錯誤,那么都有可能出現無法捕捉到的情況。這是為什么呢?原因在于 promise 內部的錯誤并不會冒泡到全局。因此,我們提供了一個 done 方法。done 方法總是處于回調鏈的尾端,保證拋出任何可能出現的錯誤。我們來看下它的使用方式,具體代碼如下:

asyncFunc().then(f1).catch(r1).then(f2).done();

同時呢,它的實現代碼也比較簡單,我們來看一下。具體代碼如下:

Promise.prototype.done = function (onFulfilled, onRejected) {this.then(onFulfilled, onRejected) .catch(function (reason) {//拋出一個全局錯誤setTimeout(() => {throw reason;}, 0);}) }

由以上代碼可知, done 方法可以像 then 方法那樣使用,提供 fulfilled 和 rejected 狀態的回調函數,也可以不提供任何參數。但是不管如何, done 方法都會捕捉到任何可能出現的錯誤,并向全局拋出。

(2)finally()

finally 方法用于指定不管 Promise 對象最后狀態如何都會執行的操作。它與 done 方法最大的區別在于,它接受一個普通的回調函數作為參數,該函數不管怎樣都必須執行。

下面來展示一個例子。假設我們現在有一臺服務器,現在讓這臺服務器使用 Promise 來處理請求,然后使用 finally 方法關掉服務器。具體實現代碼如下:

server.listen(0).then(function () {// run test }).finally(server.stop);

同樣地,它的實現代碼也比較簡單,我們來看一下。具體代碼如下:

Promise.prototype.finally = function (callback) {let p = this.constructor;return this.then(value => p.resolve(callback()).then(() => {return value;}),reason => p.resolve(callback()).then(() => {throw reason;})); };

通過以上代碼我們可以了解到,不管前面的 promise 是 fulfilled 還是 rejected ,最終都會執行回調函數 callback 。

📑三、實現Promise的核心功能

1. 基礎核心功能實現🏷?

(1)碎碎念

接下來我們先來實現 promise 最基礎的核心功能,也就是 promise.resolve() 和 promise.reject() 這兩個函數。

注意:基礎功能除了構造器 constructor 意以外,其余的實現都不是綁定在原型鏈上的函數,將會使用箭頭函數來進行實現。

(2)Promise基礎功能的分析

我們先來看下 promise 的基本使用是怎么樣的,具體代碼如下:

/*** 01_promise的基本使用*/ const promise = new Promise(function(resolve, reject) {if (success) {resolve(value);} else {reject(error);} });

根據使用方式,我們可以得出 promise 有以下幾個特點:

  • promise 是一個對象;
  • 當我們新建一個 promise 對象的同時,需要傳進去一個回調函數;
  • 這個回調函數又需要接收兩個回調函數 resolve 和 reject ,且用這兩個回調函數來作為參數,之后呢, 當調用成功時,使用 resolve 回調函數,而當調用失敗時,使用 reject 回調函數。
  • resolve 和 reject 這兩個回調函數都將會被用來修改 promise 的狀態, resolve 會把 pending 狀態修改為 fulfilled ,而 reject 將會把 pending 狀態修改為 rejected 。同時,值得注意的是,一旦狀態確定后,后續所有操作的狀態將不會再被更改,即不可逆

(3)Promise基礎功能的實現

我們現在來實現 promise 的基本功能,該功能含有以下幾個組成要素:

  • 實現 PromiseMon 的基礎結構,其中包含構造函數和狀態;
  • 實現 resolve 和 reject 功能,這里先實現狀態從 pending 到 fulfilled 或 rejected 的改變,其余狀態間的改變暫未實現。

具體實現代碼如下:

/*** 02_promise基礎功能的實現*/// 定義pending、fulfilled和rejected三個常量 const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {// 定義Promise中的狀態,默認狀態為pendingstatus = PENDING;// cb即callback,是傳給promise的回調函數constructor(cb) {// cb這個回調函數會被立即執行,作用是判斷狀態是否應該進行更改cb(this.resolve, this.reject);}// 使用箭頭函數的原因:箭頭函數可以減少this指向造成的問題,將其綁定在promise的實例對象// resolve回調函數resolve = () => {// 只有當狀態為pending時才能修改if(this.status != PENDING) {return;}else{this.status = FULFILLED;}};// reject回調函數reject = () => {只有當狀態為pending時才能修改if(this.status != PENDING) {return;}else{this.status = REJECTED;}}}// 調用resolve和reject來驗證狀態在確定之后不可逆 const promise1 = new PromiseMon((resolve, reject) => {resolve('resolved');reject('rejected'); });const promise2 = new PromiseMon((resolve, reject) => {reject('rejected');resolve('resolved'); });console.log(promise1.status); // fulfilled console.log(promise2.status); // rejected

(4)thenable功能的分析

上面我們簡單封裝了 PromiseMon 這個函數,那現在呢,我們繼續用它來實現 thenable 的功能。

大家都知道, promise 在調用了 resolve 和 reject 方法之后,就該來觸發后續的 .then 或者 .catch 方法了。如果沒有這兩個方法的話,那么 promise 返回的數據都沒啥使用的地兒,那還返回這個數據來干嘛對吧。

我們現在先來看關于 then 的基本使用操作。具體代碼如下:

const fs = require('fs');const readFile = (filename) => {const promise = new Promise(function(resolve, reject){fs.readFile(filename, (err, data) => {if(err){reject(err);}else{resolve(data);}});});return promise; }const existedFile = readFile('./test.txt'); existedFile.then((data) => {console.log('content: ', Buffer.from(data).toString());},(error) => {console.log(error);} )

綜上代碼,我們來分析 then 函數的幾個特點:

  • then 函數接收兩個參數,第一個參數在異步操作成功時進行調用,第二個則是在操作失敗時調用。
  • then 函數需要能夠分析 promise 的狀態,分析完 promise 的狀態后再決定去調用成功或失敗的回調函數
  • then 方法被定義在原型對象上: Promise.prototype.then() 。
  • then 調用成功的回調函數時會接收一個成功的數據作為參數,同樣地,當他調用失敗的回調函數時,在此之前它也會接收到一個失敗的原因來作為參數進行傳遞。
  • then 會先接收到一個成功狀態的數據,那么這個數據就用來作為參數,這個參數供給處理成功狀態的回調函數進行調用;同樣地,當處理失敗狀態時, then 會先接收到一個失敗狀態的數據,之后這個數據用來作為參數,這個參數供給處理失敗狀態的回調函數進行調用。說的這么繞,總結一下就是:接收當前狀態數據數據作為參數拿來給回調函數調用

(5)thenable功能的實現

我們現在來實現 thenable 的基本功能,該功能含有以下幾個組成要素:

  • 將在原型上實現 then 方法;
  • 修改原先的 resolve 函數,實現對成功狀態的數據進行綁定;
  • 修改原先的 reject 函數,實現對失敗狀態的原因進行綁定。

具體實現代碼如下:

/*** 03_thenable功能的實現*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;// 定義成功和失敗時的值,默認都是未定義value = undefined;reason = undefined;constructor(cb) {// cb這個回調函數會被立即執行,作用是判斷狀態是否應該進行更改cb(this.resolve, this.reject);}// 修改參數,讓resolve接收成功后傳來的值resolve = (value) => {if(this.status != PENDING) {return;}else{this.status = FULFILLED;// 將成功的值賦予給valuethis.value = value;}};// 修改參數,讓reject接收失敗后傳來的原因reject = (reason) => {if(this.status != PENDING) {return;}else{this.status = REJECTED;// 將失敗的原因賦予給reasonthis.reason = reason;}}/** then要接收兩個回調函數,successCB這個回調函數在狀態為fulfilled時使用,* 而failCB這個回調函數在狀態為rejected時進行使用*/ then(successCB, failCB) {if(this.status === FULFILLED) {successCB(this.value);}else if(this.status === REJECTED) {failCB(this.reason);}}}// 測試用例 //成功狀態測試 const successPromise = new PromiseMon((resolve, reject) => {resolve('successData');reject('failData'); });console.log('成功狀態:', successPromise.status); // 成功狀態:successDatasuccessPromise.then((value) => {console.log('success:', value); // success:successData},(reason) => {console.log('error:', reason); // 沒有輸出} )//失敗狀態測試 const failPromise = new PromiseMon((resolve, reject) => {reject('failData');resolve('successData'); });console.log('失敗狀態:', failPromise.status); // 失敗狀態:failDatafailPromise.then((value) => {console.log('success:', value); // 沒有輸出},(reason) => {console.log('error:', reason); // error:failData} )

到這里,我們就實現了一個最基礎的、且同步執行的 Promise 。接下來我們來為這個同步的 Promise 添加異步邏輯。

2. 添加異步邏輯功能實現🏷?

(1)then中添加異步邏輯功能的分析

一般來說,我們在 promise 中被調用的大部分都是異步函數,比如 setTimeout 、 setInterval 等等。所以呢,我們現在要在 then 中添加異步的功能,來實現對異步邏輯進行操作。

在上面的 then 方法中,大家定位到 if……else if…… 部分,上面所寫的邏輯只有對狀態為 fulfilled 和 rejected 時才進行判斷,而沒有對狀態為 pending 時進行判斷。

所以,當狀態為 pending 時,意味著 PromiseMon 中的異步函數還沒有執行完畢。這個時候,我們需要將 succesCB 和 failCB 這兩個回調函數給先存到一個變量中去,等到后續異步內容結束后再進行調用。

(2)then中添加異步邏輯功能的實現

依據上面的分析,我們來實現這個異步功能。具體代碼如下:

/*** 04_添加異步邏輯功能的實現*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;// 定義兩個變量,來存放成功和失敗時的回調函數successCB = undefined;failCB = undefined;constructor(cb) {cb(this.resolve, this.reject);}resolve = (value) => {if(this.status != PENDING) {return;}else{this.status = FULFILLED;this.value = value;/*** 表達式a && 表達式b:* 計算表達式a的運算結果,* 如果為true,執行表達式b,并返回b的結果;* 如果為false,返回a的結果。*//*** 當successCB里面有存放成功的回調函數時,則表明this.successCB為true,* 繼續判斷新傳來的值的狀態是否為成功狀態的值,* 如果是,則將新的值傳給this.successCB回調函數,* 如果否,則返回原來存放著的this.success的結果*/ this.successCB && this.successCB(this.value);}};reject = (reason) => {if(this.status != PENDING) {return;}else{this.status = REJECTED;this.reason = reason;// 存放調用失敗的回調函數this.failCB && this.failCB(this.reason);}}/** then要接收兩個回調函數,successCB這個回調函數在狀態為fulfilled時使用,* 而failCB這個回調函數在狀態為rejected時進行使用*/ then(successCB, failCB) {if(this.status === FULFILLED) {successCB(this.value);}else if(this.status === REJECTED) {failCB(this.reason);}// 當函數還沒有執行完畢時,只能等待else{ // 將兩個回調函數的值存放起來this.successCB = successCB;this.failCB = failCB;}}}// 測試用例 // 測試異步成功狀態 const asyncPromise1 = new PromiseMon((resolve, reject) => {setTimeout(() => {resolve('asyncSuccessData');}, 2000); });asyncPromise1.then((value) => {console.log('異步成功狀態:', value);},(reason) => {console.log('異步失敗狀態:', reason);} );// 測試異步失敗狀態 const asyncPromise2 = new PromiseMon((resolve, reject) => {setTimeout(() => {reject('asyncErrorData');}, 1000); });asyncPromise2.then((value) => {console.log('異步成功狀態:', value);},(reason) => {console.log('異步失敗狀態:', reason);} );/*** 打印結果:* 異步失敗狀態: asyncErrorData* 異步成功狀態: asyncSuccessData*/

到這里,異步的功能我們也就實現啦!但是上面的 then 我們還只是實現 then 方法的一次調用,接下來我們來實現 then 方法的多次調用。

3. 實現then方法的多次調用🏷?

(1)多次調用then方法的功能分析

多次調用 then 方法分為兩種情況:

  • 同步調用 then 方法。同步調用 then 方法相對比較簡單,只要直接調用 successCB 或 failCB 回調函數即可。
  • 異步調用 then 方法。之前的屬性 successCB 和 failCB 兩個回調函數是存放為對象形式,因此,我們需要先優化我們的存儲形式。優化完成之后,將所有的回調函數全部存放在一起,等到執行完畢之后再依次調用。

(2)多次調用then方法的功能實現

依據上述的分析,我們來實現多次調用 then 方法的功能。具體代碼如下:

/*** 05_多次調用then方法功能的實現*/const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;// 定義兩個數組變量,來各自存放成功和失敗時的所有回調函數successCB = [];failCB = [];constructor(cb) {cb(this.resolve, this.reject);}resolve = (value) => {if(this.status != PENDING) {return;}else{this.status = FULFILLED;this.value = value;// 使用 shift()方法,來彈出并返回第一個元素while(this.successCB.length){this.successCB.shift()(this.value);}}};reject = (reason) => {if(this.status != PENDING) {return;}else{this.status = REJECTED;this.reason = reason;// 使用 shift()方法,來彈出并返回第一個元素while(this.failCB.length){this.failCB.shift()(this.reason);}}}then(successCB, failCB) {if(this.status === FULFILLED) {successCB(this.value);}else if(this.status === REJECTED) {failCB(this.reason);}else{// 通過push方法將回調函數的值存放到數組中this.successCB.push(successCB);this.failCB.push(failCB);}}}// 測試用例 const multiplePromise1 = new PromiseMon((resolve, reject) => {setTimeout(() => {resolve('multiSuccessData');}, 2000); }); multiplePromise1.then((value) => {console.log('第一次調用成功:', value); // 第一次調用成功: multiSuccessData }); multiplePromise1.then((value) => {console.log('第二次調用成功:', value); // 第二次調用成功: multiSuccessData });/*** 打印結果:* 第一次調用成功: multiSuccessData* 第二次調用成功: multiSuccessData*/

講到這里,關于對此調用 then 方法的功能就實現完成了。現在,我們繼續來實現關于 then 方法的鏈式調用。

4. 實現then方法的鏈式調用🏷?

(1)then方法鏈式調用的功能分析

我們先來對 then 方法的鏈式調用進行功能分析具體如下:

  • 不考慮其他功能的前提下,先完成鏈式調用的嵌套
  • 實現鏈式調用的大前提是,每一個 then 函數返回的都必須是一個 Promise 對象,否則就無法銜接地去使用 then 函數。
  • 因此,首先我們需要在 then 函數中新建一個 Promise 對象,之后呢,在新建的 promise 對象里面,去處理內部使用的 resolve 和 reject 所返回的值,最終 then 函數也就返回了 promise 對象。

(2)then方法鏈式調用的功能實現

依據上面的功能分析,我們來實現 then 的鏈式調用功能。具體代碼如下:

/*** 06_then的鏈式調用功能的實現*/const PENDING = 'pending';const FULFILLED = 'fulfilled';const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;// 定義兩個數組變量,來各自存放成功和失敗時的所有回調函數successCB = [];failCB = [];constructor(cb) {cb(this.resolve, this.reject);}resolve = (value) => {if(this.status != PENDING) {return;}else{this.status = FULFILLED;this.value = value;while(this.successCB.length){this.successCB.shift()(this.value);}}};reject = (reason) => {if(this.status != PENDING) {return;}else{this.status = REJECTED;this.reason = reason;while(this.failCB.length){this.failCB.shift()(this.reason);}}}then(successCB, failCB) {// /*** 新建一個promise對象,來給下一個then使用。* 三種狀態:* ①promise對象執行成功,調用resolve;* ②promise對象執行失敗,則調用reject;* ③promise對象還未執行,將回調函數推入準備好的數組中。*/const thenablePromise = new PromiseMon((resolve, reject) => {if(this.status === FULFILLED) {const thenableValue = successCB(this.value);// 判斷返回的值是否是promise對象resolvePromise(thenableValue, resolve, reject);}else if(this.status === REJECTED) {const thenableReason = failCB(this.reason);// 判斷返回的值是否是promise對象resolvePromise(thenableReason, resolve, reject);}else{// 通過箭頭函數的方式將回調函數的值存放進數組中this.successCB.push(() => {const thenableValue = successCB(this.value);resolvePromise(thenableValue, resolve, reject);});this.failCB.push(() => {const thenableReason = failCB(this.reason);resolvePromise(thenableReason, resolve, reject);});}});return thenablePromise;} }/*** 判斷傳進來的thenablePromise是否是promise對象,* 如果是,則調用then函數來處理;如果否,則直接返回值。*/ const resolvePromise = (thenablePromise, resolve, reject) => {// 判斷是否是一個promise對象if(thenablePromise instanceof PromiseMon) {thenablePromise.then(resolve, reject);} else {// 如果不是promise對象,則直接返回值resolve(thenablePromise);} }// 測試用例 // 測試鏈式調用成功狀態 const thenablePromise = new PromiseMon((resolve, reject) => {resolve('thenableSuccessData'); });const otherPromise = () => {return new PromiseMon((resolve, reject) => {setTimeout(() => {resolve('otherPromise');}, 2000);}); }const anotherPromise = () => {return new PromiseMon((resolve, reject) => {setTimeout(() => {resolve('anotherPromise');});}); }thenablePromise.then((value) => {console.log('第一次鏈式調用成功:', value, new Date()); // 第一次調用成功: thenableSuccessData// return的結果是為了給下一個then使用return otherPromise();}).then((value) => {console.log('第二次鏈式調用成功:', value, new Date()); // 第二次調用成功: otherPromisereturn anotherPromise();}).then((value) => {console.log('第三次鏈式調用成功:', value, new Date()); // 第三次調用成功: anotherPromise})/*** 打印結果:* 第一次鏈式調用成功: thenableSuccessData 2021-08-04T11:13:25.868Z* 第二次鏈式調用成功: otherPromise 2021-08-04T11:13:25.877Z* 第三次鏈式調用成功: anotherPromise 2021-08-04T11:13:25.878Z*/

至此,我們就完成了 promise 的鏈式調用。

(3)鏈式調用的自我檢測

有時候我們有可能在調用 promise 時,會陷入自我調用的境地。也就是無限的循環嵌套無限的自我調用比如下面這種情況:

const promise1 = new Promise((resolve, reject) => {resolve('success'); });// 無限循環嵌套,無限自我調用 // 當運行時控制臺會拋出異常 const promise2 = promise1.then((val) => {return promise2; });// 打印結果: // TypeError: Chaining cycle detected for promise #<Promise>

因此,現在我們要做的是,在 resolvePromise 函數中新增一個參數,這個參數就是當前所創造的 promise 對象。之后判斷兩個 promise 對象是否相等,如果相等,那么就拋出異常。依據這個邏輯,我們來修改上面鏈式調用的功能代碼,達到禁止自我調用的閉環。具體代碼如下:

/*** 07_鏈式調用的自我檢測*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;successCB = [];failCB = [];//此處省略constructor代碼//此處省略resolve代碼//此處省略reject代碼then(successCB, failCB) {const thenablePromise = new PromiseMon((resolve, reject) => {/*** 利用setTimeout是異步函數的特性,* 這樣setTimeout里面的內容會在同步函數執行完之后才會進行,* 所以,當resolvePromise在執行的時候,thenablePromise就已經被實例化了,* 使得resolvePromise順利的調用thenablePromise*/if(this.status === FULFILLED) {setTimeout(() => {const thenableValue = successCB(this.value);resolvePromise(thenablePromise, thenableValue, resolve, reject);}, 0);}else if(this.status === REJECTED) {setTimeout(() => {const thenableReason = failCB(this.reason);resolvePromise(thenablePromise, thenableReason, resolve, reject);}, 0);}else {this.successCB.push(() => {setTimeout(() => {const thenableValue = successCB(this.value);resolvePromise(thenablePromise, thenableValue, resolve, reject);}, 0);});this.failCB.push(() => {setTimeout(() => {const thenableReason = failCB(this.reason);resolvePromise(thenablePromise, thenableReason, resolve, reject);}, 0);});}});return thenablePromise;}const resolvePromise = (createPromise, thenablePromise, resolve, reject) => {if(createPromise === thenablePromise) {return reject(new TypeError('出現循環調用的情況 Chaning cycle detected'))}else if(thenablePromise instanceof PromiseMon) {thenablePromise.then(resolve, reject);} else {resolve(thenablePromise);} }// 測試用例 // 測試自我調用 const thenablePromise = new PromiseMon((resolve, reject) => {resolve('chainningData'); });const chainingPromise = thenablePromise.then((value) => {console.log('數據調用成功', value, new Date());return chainingPromise; }) //會報錯,出現循環調用 chainingPromise.then((value) => {console.log('執行操作成功', value, new Date());},(reason) => {console.log('執行操作失敗', reason, new Date());} );/* 打印結果: 數據調用成功 chainningData 2021-08-04T11:28:39.984Z 執行操作失敗 TypeError: 出現循環調用的情況 Chaning cycle detected */

至此,我們完成了鏈式調用的自我檢測。

5. promise的錯誤處理🏷?

(1)錯誤處理場景

到這里,我們對 pormise 的 then 方法基本實現的差不多。但是還有一個很重要但是又很容易被我們疏忽的問題就是,錯誤處理。現在,我們來分析一下可能會出現異常的常見場景:

  • 構造器中的回調函數 cb ,需進行 try/catch 處理;
  • then 函數中的錯誤處理,需要對同步函數和異步函數進行 try/catch 處理。

(2)錯誤處理功能實現

依據上面的場景分析,我們來實現 promise 的錯誤處理功能。具體代碼如下:

/*** 08_promise的錯誤處理*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;successCB = [];failCB = [];constructor(cb) {try {cb(this.resolve, this.reject);} catch (err) {this.reject('err in cb');}}//此處省略resolve代碼//此處省略reject代碼then(successCB, failCB) {const thenablePromise = new PromiseMon((resolve, reject) => {// 給setTimeout這個異步函數添加try/catchif(this.status === FULFILLED) {setTimeout(() => {try {const thenableValue = successCB(this.value);resolvePromise(thenablePromise, thenableValue, resolve, reject);} catch (err) {reject(err);}}, 0);}else if(this.status === REJECTED) {setTimeout(() => {try {const thenableReason = failCB(this.reason);resolvePromise(thenablePromise, thenableReason, resolve, reject);} catch (err) {reject(err);}}, 0);}else {// resolvePromise 同時要加到successCB和failCB中進行處理this.successCB.push(() => {setTimeout(() => {try {const thenableValue = successCB(this.value);resolvePromise(thenablePromise, thenableValue, resolve, reject);} catch (err) {reject(err);}}, 0);});this.failCB.push(() => {setTimeout(() => {try {const thenableReason = failCB(this.reason);resolvePromise(thenablePromise, thenableReason, resolve, reject);} catch (err) {reject(err);}}, 0);});}});return thenablePromise;}// 此處省略resolvePromise代碼// 測試用例 const promise = new PromiseMon((resolve, reject) => {setTimeout(() => {resolve('successData');}); });promise.then((value) => {console.log(value); // (1) 打印 'successData'throw new Error('error'); // (2) 拋出異常},(reason) => {console.log(reason);return 'fail in then';}).then((value) => {console.log(value);},(reason) => {console.log(reason);return 'callback fail'; // 拋出異常后返回 'callback fail' 給下面的then方面調用}).then((value) => {console.log(value);},(reason) => {console.log(reason); // (3)上面傳來callback fail,因此打印 'callback fail'});/* 打印結果: successData Error: error callback fail */

大家可以看到,通過 try/catch 的方式,對遇到的錯誤進行處理,并且最終拋出異常以及返回 reason 的值。

6. 實現then方法的參數可選🏷?

(1)參數可選實現思路

大家可以發現,上面我們在調用 then 方法的時候,一直都是需要進行參數傳遞的,這樣看起來好像還不是特別友好。因此呢,我們現在來實現這個功能,讓 then 方法的參數可以有傳或者不傳這 2 種操作。實現思路也比較簡單,就是在 then 函數中判斷是否傳入參數,如果沒有的話,則返回原來的 value 就好了。類似于下面這種形式:

promise.then((val) => val) // 這樣使用箭頭函數表明直接返回 value.then((val) => val) .then((val) => val) .then((val) => {console.log(val); // 200});

(2)參數可選功能實現

依據上面的是實現思路,接下來我們來實現這個功能。具體代碼如下:

下面我們對 then 函數進行改造:

then(successCB, failCB) {successCB = successCB ? successCB : (value) => value;failCB = failCB ? failCB : (reason) => { throw reason;}; }

來用兩組測試用例進行測試:

// 測試用例 // 成功狀態下的用例const successpromise = new PromiseMon((resolve, reject) => {resolve(100);});successpromise.then().then().then().then((val) => {console.log(val); // 100});// 失敗狀態下的用例 const failPromise = new PromiseMon((resolve, reject) => {reject(200); });failPromise.then().then().then().then((val) => {},(reason) => {console.log(reason); // 200}); /*** 打印結果:* 100* 200 */

大家可以看到,不管是 resolve 還是 reject 狀態,都一一完成了對參數可選功能的實現。

7. 實現Promise.all🏷?

(1)Promise.all功能分析

上面在講 Promise 異步方案的時候就已經講過 promise.all 和 promise.race 方法。現在,我們來梳理下實現思路:

  • Promise.all 是一個靜態方法,它接收一個 Promise 數組 作為參數。
  • Promise.all 的特點在于,它可以按照順序去獲取所有調用的異步函數。
  • js 的關鍵字 static 將會把對應的變量或函數綁定到class類上,而不是綁定在 ** prototype 原型**上。

(2)Promise.all功能實現

依據實現的這個邏輯,來實現 promise.all 這個功能。具體代碼如下:

/*** 10_promise.all功能的實現*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;successCB = [];failCB = [];//省略constructor、resolve、reject和then方法的代碼static all(arr){const results = [];let index = 0;return new PromiseMon((resolve, reject) => {// 添加數據的邏輯,把指定的數據添加到數組的對應位置上const addData = (idx, val) => {results[idx] = val;index++;// 進行這一步判斷的目的:為了等待異步操作完成if(index === arr.length) {resolve(results);}}// 對數組進行循環,獲取所有的數據arr.forEach((cur, index) => {// 如果傳進來的值是一個promise對象if(cur instanceof PromiseMon){cur.then((value) => addData(index, value),(reason) => reject(reason))}// 如果傳進來的是普通值,而非promise對象else {addData(index, cur);}});});} }// 此處省略resolvePromise代碼// 測試用例 const promise = () => {return new PromiseMon((resolve, reject) => {resolve(100);}); }const promise2 = () => {return new PromiseMon((resolve, reject) => {setTimeout(() => {resolve(200);}, 1000);}); }//因為使用static關鍵字,所以可以直接在類上面進行調用 PromiseMon.all(['a', 'b', promise(), promise2(), 'c']).then((res) => {console.log(res); // [ 'a', 'b', 100, 200, 'c' ] })

大家可以看到,即使 promise2 是異步函數,但最終也正常的顯示在數組當中,且按序的一一進行打印。到此,也就說明 promise.all 成功實現啦!

同時, promise.race 也是按照這個模式去實現,這里不再進行講解~

8. 實現Promise.resolve🏷?

(1)Promise.resolve功能分析

我們先來梳理下 promise.resolve 的實現思路:

  • 如果參數是一個 promise 實例,那么 promise.resolve() 將不做任何修改,原封不動地返回這個實例。
  • 參數不是 promise 實例,或根本不是一個對象,則 Promise.resolve() 方法返回一個新的 promise 對象,此時狀態為 fulfilled 。
  • 不帶有任何參數,則直接返回一個 fulfilled 狀態的 promise 對象。

(2)Promise.resolve功能實現

依據以上的功能分析,來實現 promise.resolve 這個功能。具體代碼如下:

/*** 11_promise.resolve功能的實現*/const PENDING = 'pending'; const FULFILLED = 'fulfilled'; const REJECTED = 'rejected';class PromiseMon {status = PENDING;value = undefined;reason = undefined;successCB = [];failCB = [];//省略constructor、resolve、reject和then方法的代碼static resolve(value){// 傳進來的是一個promise對象,原封不動返回if(value instanceof PromiseMon) {return value;} // 傳進來的不是promise對象,將其作為參數返回一個promiseelse {return new PromiseMon((resolve, reject) => {resolve(value);})}} }// 此處省略resolvePromise代碼// 測試用例 const promise = () => {return new PromiseMon((resolve, reject) => {resolve(100);}); }// 1.參數是一個promise實例,那么不做任何修改,原封不動地返回這個實例 PromiseMon.resolve(promise).then((res) => {console.log(res); // 100 })/*** 2.參數不是具有then方法的對象,或根本就不是對象,* 則Promise.resolve()方法返回一個新的promise對象,狀態為fulfilled*/ PromiseMon.resolve(200).then((res) => {console.log(res); // 200 })/*** 3.不帶有任何參數,則直接返回一個resolved狀態的promise對象*/ PromiseMon.resolve().then(function () {console.log('two'); // two });

大家可以看到,依據我們所羅列的三種情況, promise.resolve 的功能也一一實現啦!

同時, promise.reject 也是按照這個模式去實現,這里不再進行講解~

📝四、結束語

寫到這里的時候,發現我已經花了整整三天的時間,大約接近34h+在 promise 這個知識上,好在最終算是對 promise 核心功能的的實現有一個較為滿意的結果。

可能也是第一次這么細致的去啃一個知識,所以在學習過程中遇到很多以前沒踩過的坑,中間過程中對問題進行詳細記錄并嘗試解決,慢慢的就完善了一個新的知識體系。

最后,本文講解到這里就結束啦!希望大家能對 promise 有一個更好的了解~

🐣彩蛋 One More Thing

(:課代表記錄

?《三 7.》的 Promise.race 方法未用代碼在原文中實現。

?《三 8.》中,據資料調查, promise.resolve 還有第4種傳參方式,當參數是一個 thenable ,即參數是一個帶有 then 方法的對象時,則結果返回具有 then 方法的對象(本功能暫未實現)。

?《三 8.》的 Promise.reject 方法未用代碼在原文中實現。

(:參考資料

👉 [萬字詳解]JavaScript 中的異步模式及 Promise 使用

👉 [1w6k 字詳細講解] 保姆級一步一步帶你實現 Promise 的核心功能

👉 ES6快速入門

(:番外篇

  • 關注公眾號星期一研究室,第一時間關注優質文章,更多精選專欄待你解鎖~
  • 如果這篇文章對你有用,記得留個腳印jio再走哦~
  • 以上就是本文的全部內容!我們下期見!👋👋👋

總結

以上是生活随笔為你收集整理的「3.4w字」超保姆级教程带你实现Promise的核心功能的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。