async 和 await 的本質

2022-08-11 10:24:16 字數 3384 閱讀 1223

​    async 和 await 幾乎是 nodejs 的最典型關鍵字,最能代表 nodejs 的特色,然而掌握這兩個關鍵字的原理卻不容易。這篇文章使用從零「構建」出 async 和 await 關鍵字的方式,來幫助理清 async 和 await 的本質。

先用一句話概括:async 和 await 是內建了執行器的 generator 函式。

什麼是 generator 函式?顧名思義,generator 函式就是乙個生成器。生成的是乙個可以多次通過 .next() 迭代的物件,例如,定義乙個 generator 函式如下:

let g = function*()

其中,yield 關鍵字定義每次迭代的返回值,最後乙個返回值用 return。

然後,就可以用它來生成乙個可迭代的物件:

let iter =g()

​console.log(iter.next())

console.log(iter.next())

console.log(iter.next())

console.log(iter.next())

以上**執行的結果是:

generator 函式也可以接收引數:

let g = function*(a, b) 

​let iter = g(1, 2)

​console.log(iter.next())

console.log(iter.next())

console.log(iter.next())

console.log(iter.next())

執行結果:

接下來是乙個關鍵點:前面的例子中,呼叫next() 時並沒有傳遞引數,但是實際上 next() 是可以接受引數的,而且這個引數和 yield 關鍵字有特殊的關係:

let g = function*() 

​let iter =g()

​console.log(iter.next())

console.log(iter.next(2))

以上**的執行結果是:

可以看到,next(2) 這個呼叫導致 ret 的值變成了2。這是為什麼呢?因為 next() 的引數會成為 yield 表示式的值。也就是說,

let ret = yield 1

這行**其實是被拆成兩段執行的。第一次呼叫 .next() 的時候,執行到了 yield 1 這裡,就暫停並返回了。這時列印 .next() 的返回值是 。然後,執行 .next(2) 的時候,又回到了 g 裡面的**,從 let ret = 2 開始執行。

理清楚這一執行過程非常重要。因為,這意味著:

如果我在 g 裡面 yield 乙個 promise 出去,在外面等 promise 執行完之後,再通過 .next() 的引數把結果傳進來,會怎樣呢?

let asyncsum = function

(a, b) , 1000)

})}​

let g = function*()

​let iter =g()

​let p =iter.next().value

p.then(sum =>)

執行結果就是等待一秒之後列印出3:

//

這裡掛起了一秒鐘

請細細品味上面**裡面的 g 函式:

let g = function*()

將其與下面**進行對比:

let g = async function

()

是不是特別相似?事實上, async 函式的本質就是 generator 函式,只不過附帶了乙個執行器。

這裡就引出了執行器的概念。什麼叫執行器?讓我們回到 g:

let g = function*() 

g 作為乙個 generator 函式,是有兩段的。如果我們想要把 g 從頭到尾執行完,需要這樣子呼叫 g:

let iter =g()

​let p =iter.next().value // 第一次呼叫 next(),先執行第一段

p.then(sum =>)

這樣就特別麻煩,最好有乙個函式 executor(),我們可以把 g 當作引數傳給它,它會自動把 g 執行完,再把最後的結果返回回來。就像這樣:

executor(g).then(result => )

有沒有這樣的函式呢?有的,我們把上面那段**自己封裝一下,就可以自己寫出乙個 executor 來:

let executor = function

(g) )

})}​

executor().then(ret =>)

這個 executor 就叫做 g 的執行器。當然啦,這個執行器只是適用於 g,不夠通用,能不能做得更通用一點,使它能執行任何的 generator 函式呢?可以的,如下:

let asyncsum = function

(a, b) , 1000)

})}​

let asyncmul = function

(a, b) , 1000)

})}​

let g = function*(a, b)

​function

executor(generator, ...args)

else

); });

}}​function

_r(iter, ret, resolve)

else

) }}​

executor(g, 1, 2).then(ret =>)

執行結果:

//

這裡掛起了兩秒鐘

6

co(function*() ).then(

function

(value) ,

function

(err) );

所以 async 函式本質上就是內建了執行器的 generator 函式,只不過 nodejs 引擎幫我們實現了執行器。當我們呼叫 async 函式時,引擎內部呼叫了執行器。

原理到這裡就結束了。不過可能有細心的讀者發現乙個奇怪的現象:為什麼 tj holowaychuk 的這個模組名字要叫做 co?

答案是 co 代表 coroutine,也就是協程啦。理解到這裡就又更深入一層了,但是這裡不展開啦,async 函式是協程在 nodejs 中的實現形式。

async和await的講解

普通的函式宣告 async function a 複製 宣告乙個函式表示式 let a async function 複製 async形式的箭頭函式 let a async 複製 async與await例項應用,基礎 控制器呼叫與server中查詢資料 exports.getbloglist asy...

async和await的使用

async其實是es7的才有的,是非同步操作的進化,其實就是封裝乙個promise的物件返回 async function test console.log test promiseasync方法在普通的函式前加上 async 關鍵字即可。執行這個函式,發現並沒有返回1111,而是通過promise...

async和await的講解

async和await的講解 宣告async函式的幾個方法 普通的函式宣告 async function a 宣告乙個函式表示式 let a async function async形式的箭頭函式 let a async 初識async和await async與await例項應用,基礎 控制器呼叫與...