javascript
nodejs全局变量第一次没赋值要第二次才有是为什么_【NodeJS】async 和 await 的本质...
對于許多新接觸 NodeJS 的人而言,async 和 await 的原理是比較難理解的。本文將從零“構建”出 async 和 await 關鍵字,從而幫助理清 async 和 await 的本質。
先用一句話概括:async 和 await 是內置了執(zhí)行器的 generator 函數(shù)。
什么是 generator 函數(shù)?顧名思義,generator 函數(shù)就是一個生成器。生成的是一個可以多次通過 .next() 迭代的對象,例如,定義一個 generator 函數(shù)如下:
let g = function* () {yield 1yield 2return 3 }其中,yield 關鍵字定義每次迭代的返回值,最后一個返回值用 return。
然后,就可以用它來生成一個可迭代的對象:
let iter = g() ? console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())以上代碼執(zhí)行的結果是:
{ value: 1, done: false } { value: 2, done: false } { value: 3, done: true } { value: undefined, done: true }generator 函數(shù)也可以接收參數(shù):
let g = function* (a, b) {yield ayield breturn a + b } ? let iter = g(1, 2) ? console.log(iter.next()) console.log(iter.next()) console.log(iter.next()) console.log(iter.next())執(zhí)行結果:
{ value: 1, done: false } { value: 2, done: false } { value: 3, done: true } { value: undefined, done: true }接下來是一個關鍵點:yield 關鍵字的值和 .next() 的參數(shù)的關系:
let g = function* () {let ret = yield 1return ret } ? let iter = g() ? console.log(iter.next()) console.log(iter.next(2))以上代碼的執(zhí)行結果是:
{ value: 1, done: false } { value: 2, done: true }可以看出,第二次調用 .next() 的時候,傳入了參數(shù)2,這個 2 被賦值給了 ret。也就是說,
? let ret = yield 1這行代碼其實是被拆成兩段執(zhí)行的。第一次調用 .next() 的時候,g 里面的代碼開始執(zhí)行,執(zhí)行到了 yield 1 這里,就暫停并返回了。這時打印 .next() 的返回值是 { value: 1, done: false }。然后,執(zhí)行 .next(2) 的時候,又回到了 g 里面的代碼,從 let ret = 2 開始執(zhí)行。
理清楚這一執(zhí)行過程非常重要。因為,這意味著:
如果我在 g 里面 yield 一個 Promise 出去,在外面等 Promise 執(zhí)行完之后,再通過 .next() 的參數(shù)把結果傳進來,會怎樣呢?
let asyncSum = function(a, b) {return new Promise(resolve => {setTimeout(() => {resolve(a + b)}, 1000)}) } ? let g = function* () {let ret = yield asyncSum(1, 2)console.log(ret)return ret } ? let iter = g() ? let p = iter.next().value p.then(sum => {iter.next(sum) })執(zhí)行結果就是等待一秒之后打印出3:
// 這里掛起了一秒鐘 3請細細品味上面代碼里面的 g 函數(shù):
let g = function* () {let ret = yield asyncSum(1, 2)console.log(ret)return ret }將其與下面代碼進行對比:
let g = async function () {let ret = await asyncSum(1, 2)console.log(ret)return ret }發(fā)現(xiàn)了吧?事實上 async 函數(shù)就是 generator 函數(shù)。
然而有人會問,我們調用 async 函數(shù),都是直接調用,返回一個 Promise ,而不用像上面調用 g 那么麻煩的。
沒錯。上面調用 g 的代碼:
let iter = g() ? let p = iter.next().value p.then(sum => {iter.next(sum) })叫做 g 的執(zhí)行器。我們可以把它封裝起來:
let executor = function() {return new Promise(resolve => {let iter = g() ?let p = iter.next().valuep.then(sum => {let ret = iter.next(sum)resolve(ret.value)})}) } ? executor().then(ret => {console.log(ret) })執(zhí)行結果:
// 掛起一秒鐘 3 // g 里面的 console.log(ret) 3 // .then 里面的 console.log(ret)實際上,node的執(zhí)行引擎悄悄地幫我們做了上面的事情,當我們直接調用一個 async 函數(shù)時,其實是在調用它的執(zhí)行器。
原理講到這里就完了。下面是擴展部分。
上面的 executor 函數(shù)是僅僅針對這個例子里面的 g 寫的。那我們是否可能寫一個通用的執(zhí)行器函數(shù),適用于任何 generator 函數(shù)呢?不管 generator 函數(shù)里面有多少個 yield ,這個執(zhí)行器是否都可以自動全部處理完?
答案當然是肯定的,用到了遞歸,請看完整代碼:
let asyncSum = function(a, b) {return new Promise(resolve => {setTimeout(() => {resolve(a + b)}, 1000)}) ?} ? let asyncMul = function(a, b) {return new Promise(resolve => {setTimeout(() => {resolve(a * b)}, 1000)}) } ? let g = function* (a, b) {let sum = yield asyncSum(1, 2)let ret = yield asyncMul(sum, 2)return ret } ? function executor(generator, ...args) {let iter = generator.apply(this, args)let n = iter.next()if (n.done) {return new Promise(resolve => resolve(n.value))} else {return new Promise(resolve => {n.value.then(ret => {_r(iter, ret, resolve)});});} } ? function _r(iter, ret, resolve) {let n = iter.next(ret)if (n.done) {resolve(n.value)} else {n.value.then(ret => {_r(iter, ret, resolve)})} } ? executor(g, 1, 2).then(ret => {console.log(ret) })執(zhí)行結果:
// 這里掛起了兩秒鐘 6不過上面這個 executor 是個不完善的版本,因為沒有考慮錯誤的情況。其實早在 async 和 await 還沒有出現(xiàn)的 2013 年,著名程序員 TJ Holowaychuk 就寫了一個完善的 generator 執(zhí)行器。項目地址:https://github.com/tj/co 。其名字叫 co。典型用法就是:
co(function* () {var result = yield Promise.resolve(true);return result; }).then(function (value) {console.log(value); }, function (err) {console.error(err.stack); });關于 async 和 await 的本質,到這里就結束了。文章最后請有興趣的讀者思考一個問題:為什么 TJ Holowaychuk 的這個模塊名字要叫做 co?
總結
以上是生活随笔為你收集整理的nodejs全局变量第一次没赋值要第二次才有是为什么_【NodeJS】async 和 await 的本质...的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: jupyter代码字体大小_Jupyte
- 下一篇: 场景法设计测试用例atm_测试用例设计经