ES2017 標(biāo)準(zhǔn)引入了 async 函數(shù),使得異步操作變得更加方便。
async 函數(shù)是什么?一句話,它就是 Generator 函數(shù)的語法糖。研究 async 的原理,就必須先弄清楚 Generator 是個(gè)啥。
Generator 函數(shù)是 ES6 提供的一種異步編程解決方案,語法行為與傳統(tǒng)函數(shù)完全不同。
形式上,Generator 函數(shù)是一個(gè)普通函數(shù),但是有兩個(gè)特征。一是,function關(guān)鍵字與函數(shù)名之間有一個(gè)星號(hào);二是,函數(shù)體內(nèi)部使用yield表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”)
看一個(gè)例子:
function* gen(x) { var y = yield x + 2; return y;}var g = gen(1);g.next() // { value: 3, done: false }g.next() // { value: undefined, done: true }
上面代碼中,調(diào)用 Generator 函數(shù),會(huì)返回一個(gè)內(nèi)部指針(即遍歷器)g。這是 Generator 函數(shù)不同于普通函數(shù)的另一個(gè)地方,即執(zhí)行它不會(huì)返回結(jié)果,返回的是指針對(duì)象。調(diào)用指針g的next方法,會(huì)移動(dòng)內(nèi)部指針(即執(zhí)行異步任務(wù)的第一段),指向第一個(gè)遇到的yield語句,上例是執(zhí)行到x + 2為止。
換言之,next方法的作用是分階段執(zhí)行Generator函數(shù)。每次調(diào)用next方法,會(huì)返回一個(gè)對(duì)象,表示當(dāng)前階段的信息(value屬性和done屬性)。value屬性是yield語句后面表達(dá)式的值,表示當(dāng)前階段的值;done屬性是一個(gè)布爾值,表示 Generator 函數(shù)是否執(zhí)行完畢,即是否還有下一個(gè)階段。
這樣手工的執(zhí)行next()函數(shù),著實(shí)有些麻煩,能寫個(gè)工具讓他自動(dòng)執(zhí)行嗎?那我們就來試試:
封裝一個(gè) spawn 函數(shù),返回一個(gè) spawn 函數(shù),給函數(shù)傳入 Generator函數(shù)作為參數(shù),spawn 實(shí)現(xiàn) next() 方法的執(zhí)行。
function fn(args) { return spawn(function* () { // ... });}
spawn 函數(shù)的實(shí)現(xiàn):
function spawn(genF) { return new Promise(function(resolve, reject) { const gen = genF(); function step(nextF) { let next; try { next = nextF(); } catch(e) { return reject(e); } if(next.done) { return resolve(next.value); } Promise.resolve(next.value).then(function(v) { step(function() { return gen.next(v); }); }, function(e) { step(function() { return gen.throw(e); }); }); } step(function() { return gen.next(undefined); }); });}
應(yīng)用這個(gè)方法執(zhí)行一下第一個(gè)例子:
function fn(x) { return spawn(function* gen() { var y = yield x + 2 return y; });}fn(1).then((result) => { console.log(result) // 3})
如果 yield 后面是個(gè) Promise, 就可以實(shí)現(xiàn)異步了:
function fn(x) { return spawn(function* gen() { var y = yield new Promise((resolve) => { setTimeout(() => { resolve(x + 1) }, 1000) }) return y; });}fn(1).then((result) => { console.log(result) // 過一秒后打印 3})
這樣,過一秒后就打印 3 了。
從整個(gè)代碼上來看,實(shí)現(xiàn)起來有些麻煩。Async 簡化了一切,使用它,不再需要 spawn 函數(shù),只需將 Generator 函數(shù)的星號(hào)(*)替換成async,將yield替換成await,僅此而已。改造一下:
async function fn(x) { let result = await new Promise((resolve) => { setTimeout(() => { resolve(x + 2) }, 1000) }) return result}fn(1).then((result) => { console.log(result)})
真是簡潔了很多。
最后看一個(gè)面試題,如何將程序的執(zhí)行結(jié)果 1,3,2,改造為 1,2, 3
只需修改一個(gè) onGetUser 函數(shù)即可:
async onGetUser() { // getUser().then((result) => { // console.log(result) // }) let result = await getUser() console.log(result)}