🧑💻 寫在開頭
點贊 + 收藏 === 學會🤣🤣🤣
本文用於記錄在React課程中學習時,課程中留下的一個關於
async/await
原理的思考題(預設讀者熟悉Promise
)
思考題
這個思考題就是:請將以下async/await
程式碼,換一種方式實現,保證非同步等待功能和輸出順序:
function delay(ms, data) { return new Promise(resolve => setTimeout(resolve, ms, data)); } const func = async() => { const data = await delay(2000, 'A'); console.log(data); const res = await delay(2000, 'B'); console.log(res); }; func();
這裡可以先暫停去實現一下,以下內容從async/await
基本知識開始。
async/await基本介紹
async/await
是一種以更舒服的方式使用promise
的特殊語法,讓非同步邏輯更加簡潔可讀,避免promise
的鏈式寫法。
async
首先來介紹async
,該關鍵字代表函式總是返回promise
,返回的promise
有resolved
的情況和rejected
的情況:
resolved
情況如下:- 若函式返回了值,則該值會被
Promise.resolve
包裝,被解決的值就是該函式返回的值 - 若函式沒有返回值,則
promise
中被解決的值為undefined
- 若函式返回了值,則該值會被
// 返回值 const func = async () => { return 1; } // 控制檯列印:Promise {<fulfilled>: 1} 'func' console.log(func(), 'func'); // ------------------------------------------------------ // 沒有返回 const func1 = async () => { } // 控制檯列印:Promise {<fulfilled>: undefined} 'func1' console.log(func1(), 'func1');
rejected
情況:- 返回錯誤或是丟擲錯誤,會導致這個
promise
為rejected
- 返回錯誤或是丟擲錯誤,會導致這個
// ---------------------------返回錯誤--------------------------- const func = async () => { return new Error('error'); } // 控制檯列印:Promise {<fulfilled>: Error: error console.log(func(), 'func') // ---------------------------丟擲錯誤--------------------------- const func1 = async () => { throw new Error('error'); } // 控制檯列印:Promise {<fulfilled>: Error: error console.log(func1(), 'func1')
await
與async
配對使用的就是await
,並且await
只能在async
函式內工作,作用是等待promise
完成並返回結果,這裡也分resolved
的情況和rejected
的情況:
resolved
情況:promise
的resolved
情況,被解決的值作為await
表示式的值
const func = async () => { // await表示式的值就是被解決值'done',然後被賦值給data const data = await Promise.resolve('done'); }
rejected
情況:promise
的rejected
情況,如果不使用try/catch
捕獲,則語句(1)等同於語句(2)的效果,都會丟擲錯誤
const func = async () => { // 控制檯:Uncaught (in promise) error const data = await Promise.reject('error'); (1) throw 'error'; (2) }
關鍵點
熟悉async/await
之後,就是要準備實現它了;在實現它之前,不妨將目前的特點總結一下:
- 處理的是
Promise
- 能夠暫停函式執行
- 能夠等待
Promise
解決之後,取出解決值,恢復函式執行
縱觀以上的特點,關鍵點就在於函式的暫停和恢復執行,只要解決它,就能夠實現async/await
一樣的效果;
查閱資料能發現,在JavaScript
中有一個能夠實現函式的暫停與執行的,那就是Generator(生成器)
,所以接下來先了解一下Generator
的基本語法。
Generator簡介
Generator
:譯為生成器,是ECMAScript 6
新增的一個極為靈活的結構,擁有在一個函式塊內暫停和恢復程式碼執行的能力;基礎程式碼示例如下:
const func = function* (){ yield 1; yield 2; yield 3; } const iterator = func(); iterator.next(); // {value: 1, done: false} iterator.next(); // {value: 2, done: false} iterator.next(); // {value: 3, done: false} iterator.next(); // {value: undefined, done: true}
Generator
有以下特點:
- 宣告生成器函式需要使用
function* 函式名()
語法,其實function *函式名()
也可以,因為是函式的特殊語法,所以建議使用前者*
靠近function
的寫法 - 生成器函式被呼叫的時候,函式並不會執行,而是返回一個生成器例項
Generator
是Iterator的子類,所以生成器例項具有迭代器的特性- 生成器例項具有
next、return、throw
方法,其主要方法就是next
;當next
被呼叫時,會恢復函式執行,執行到最近的yield
,然後暫停,並將yield
後的結果返回到外部,也就是next
呼叫後的value
值 yield
既可以產出值,也可以輸入值;給next
方法傳入的值,作為上一個yield表示式
的值
接下來看一個使用next
傳入值,yield
接收值的例子,也請思考一下列印結果:
const func = function* () { console.log(1); const data = yield 2; console.log(data); yield 4; } const it = func(); console.log(it.next()); console.log(it.next(3)); console.log(it.next());
可能這裡的列印順序以及邏輯處理,對之前沒有接觸過生成器知識的朋友有點不知所以,接下來,我來對程式碼的執行做一個解釋(這裡用「」
代表行數,例如:「8」
表示第8行):
- 執行
「8」
:執行生成器函式,生成生成器例項,此時函式內部並未執行 - 執行
「10」
:- 先呼叫
next
方法,函式開始執行 - 執行
「2」
,列印1 - 執行
「3」
,遇到yield 2
,暫停執行,返回內容 - 執行
「10」
,列印{value: 2, done: false}
- 先呼叫
- 執行
「11」
:- 先呼叫
next
方法,函式從上一次暫停處「3」
恢復執行 - 執行
「3」
,next中的引數作為yield 2
表示式的值;data被賦值為3 - 執行
「4」
,列印3 - 執行
「5」
,遇到yield 4
,暫停執行,返回內容 - 執行
「11」
,列印{value: 4, done: false}
- 先呼叫
- 執行
「12」
:- 先呼叫
next
方法,函式從上一次暫停處恢復執行 - 無執行內容,迭代結束
- 執行
「12」
,列印{value: undefined, done: true}
- 先呼叫
實現
思路
經過以上的步驟,對Generator
的暫停和執行的特點有了認識,現在來講解一下實現思考題的思路。
觀察之前的這段程式碼:
const func = function* () { console.log(1); const data = yield 2; console.log(data); yield 4; } const it = func(); console.log(it.next()); console.log(it.next(3)); console.log(it.next());
可以發現「3」
就比較類似業務程式碼中的const {data} = await API.xxx()
形式,兩者都有等待後表示式的值賦值給左側的特點;
- 等待後賦值
關鍵點就在這個“等待後賦值”
上,將上面程式碼改造為的讓data
等待一會兒再被賦值,如下:
const func = function* () { console.log(1); const data = yield 2; console.log(data); yield 4; } const it = func(); console.log(it.next()); // 等待3s再執行 setTimeout(() => { console.log(it.next(3)); console.log(it.next()); }, 3000);
以上程式碼讓3s之後再執行next(3)
給data
賦值,也就是再被賦值之前,操作空間很大,完全可以等待一些事件完成之後再呼叫next(3)
將值傳入函式內部,且讓函式內部繼續執行。
如果讀者的思路一直跟到這裡,那麼我相信讀者對如何用Generator
和Promise
實現async/await
已經有了一些思路了,不妨先去動手試試,再來看下面的具體程式碼。
具體程式碼
那麼用Generator
和Promise
實現文章開頭的思考題,如下所示:
function delay(ms, data) { return new Promise(resolve => setTimeout(resolve, ms, data)); } const func = function* () { const data = yield delay(2000, 'A'); console.log(data); const res = yield delay(2000, 'B'); console.log(res); } let p1, p2, it = func(); // 接收第一個Promise p1 = it.next().value; p1.then((res) => { // 給data賦值,接收第二個Promise p2 = it.next(res).value; p2.then((res) => { // 執行到最後 it.next(res); }); });
async/await與Generator/Promise的關係
到這裡,思考題的意圖就顯現出來了;目的就是點出async/await
的原理其實就是Generator+Promise
;再換一句話描述就是:async/await
是Generator+Promise
的語法糖。
如果對您有所幫助,歡迎您點個關注,我會定時更新技術文件,大家一起討論學習,一起進步。