面试官问 async、await 函数原理是在问什么?
大家好,我是若川。這是 源碼共讀活動(dòng)《1個(gè)月,200+人,一起讀了4周源碼》 第四期,紀(jì)年小姐姐的第四次投稿。紀(jì)年小姐姐通過本次學(xué)習(xí)提早接觸到generator,協(xié)程概念,了解了async/await函數(shù)的原理等。
第四期是 學(xué)習(xí) koa 源碼的整體架構(gòu),淺析koa洋蔥模型原理和co原理中的co原理。不知不覺,源碼共讀已經(jīng)進(jìn)行了一個(gè)月,有些小伙伴表示對(duì)面試和工作很有幫助,學(xué)完立馬能用。如果你也感興趣可以加我微信 ruochuan12參加。
1. 前言
這周看的是 co 的源碼,我對(duì) co 比較陌生,沒有了解和使用過。因此在看源碼之前,我希望能大概了解 co 是什么,解決了什么問題。
2. 簡(jiǎn)單了解 co
先看了 co 的 GitHub,README 是這樣介紹的:
Generator based control flow goodness for nodejs and the browser, using promises, letting you write non-blocking code in a nice-ish way.
看起來有點(diǎn)懵逼,又查了一些資料,大多說 co 是用于 generator 函數(shù)的自動(dòng)執(zhí)行。generator 是 ES6 提供的一種異步編程解決方案,它最大的特點(diǎn)是可以控制函數(shù)的執(zhí)行。
2.1 關(guān)于 generator
說到異步編程,我們很容易想到還有 promise,async 和 await。它們有什么區(qū)別呢?先看看 JS 異步編程進(jìn)化史:callback -> promise -> generator -> async + await
JS 異步編程再看看它們語法上的差異:
| ajax(url, () => {}) | Promise((resolve,reject) => { resolve() }).then() | function* gen() { yield 1} | async getData() { ?await fetchData() } |
關(guān)于 generator 的學(xué)習(xí)不在此篇幅詳寫了,需要了解它的概念和語法。
3. 學(xué)習(xí)目標(biāo)
經(jīng)過簡(jiǎn)單學(xué)習(xí),大概明白了 co 產(chǎn)生的背景,因?yàn)?generator 函數(shù)不會(huì)自動(dòng)執(zhí)行,需要手動(dòng)調(diào)用它的 next() 函數(shù),co 的作用就是自動(dòng)執(zhí)行 generator 的 next() 函數(shù),直到 done 的狀態(tài)變成 true 為止。
那么我這一期的學(xué)習(xí)目標(biāo):
1)解讀 co 源碼,理解它是如何實(shí)現(xiàn)自動(dòng)執(zhí)行 generator
2)動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)略版的 co
4. 解讀 co 源碼
co 源碼地址:https://github.com/tj/co
4.1 整體架構(gòu)
從 README 中,可以看到是如何使用 co :
co(function*?()?{var?result?=?yield?Promise.resolve(true);return?result; }).then(function?(value)?{console.log(value); },?function?(err)?{console.error(err.stack); });從代碼可以看到它接收了一個(gè) generator 函數(shù),返回了一個(gè) Promise,這部分對(duì)應(yīng)的源碼如下。
function?co(gen)?{var?ctx?=?this;//?獲取參數(shù)var?args?=?slice.call(arguments,?1);//?返回一個(gè)?Promisereturn?new?Promise(function(resolve,?reject)?{//?把?ctx?和參數(shù)傳遞給?gen?函數(shù)if?(typeof?gen?===?'function')?gen?=?gen.apply(ctx,?args);//?判斷?gen.next?是否函數(shù),如果不是直接?resolve(gen)if?(!gen?||?typeof?gen.next?!==?'function')?return?resolve(gen);//?先執(zhí)行一次?nextonFulfilled();//?實(shí)際上就是執(zhí)行?gen.next?函數(shù),獲取?gen?的值function?onFulfilled(res)?{var?ret;try?{ret?=?gen.next(res);}?catch?(e)?{return?reject(e);}next(ret);return?null;}//?對(duì)?gen.throw?的處理function?onRejected(err)?{var?ret;try?{ret?=?gen.throw(err);}?catch?(e)?{return?reject(e);}next(ret);}//?實(shí)際處理的函數(shù),會(huì)遞歸執(zhí)行,直到?ret.done?狀態(tài)為?truefunction?next(ret)?{//?如果生成器的狀態(tài)?done?為?true,就?resolve(ret.value),返回結(jié)果if?(ret.done)?return?resolve(ret.value);//?否則,將?gen?的結(jié)果?value?封裝成?Promisevar?value?=?toPromise.call(ctx,?ret.value);//?判斷?value?是否?Promise,如果是就返回?thenif?(value?&&?isPromise(value))?return?value.then(onFulfilled,?onRejected);//?如果不是?Promise,Rejectedreturn?onRejected(new?TypeError('You?may?only?yield?a?function,?promise,?generator,?array,?or?object,?'+?'but?the?following?object?was?passed:?"'?+?String(ret.value)?+?'"'));}}); }看到這里,我產(chǎn)生了一個(gè)疑問:Promise + then 也可以處理異步編程,為什么 co 的源碼里要把 Promise + generator 結(jié)合起來呢,為什么要這樣做?直到我搞懂了 co 的核心目的,它使 generator 和 yield 的語法更趨向于同步編程的寫法,引用阮一峰的網(wǎng)絡(luò)日志中的一句話就是:
異步編程的語法目標(biāo),就是怎樣讓它更像同步編程。
可以看一個(gè) Promise + then 的例子:
function?getData()?{return?new?Promise(function(resolve,?reject)?{resolve(1111)}) } getData().then(function(res)?{//?處理第一個(gè)異步的結(jié)果console.log(res);//?返回第二個(gè)異步return?Promise.resolve(2222) }) .then(function(res)?{//?處理第二個(gè)異步的結(jié)果console.log(res) }) .catch(function(err)?{console.error(err); })如果有多個(gè)異步處理就會(huì)需要寫多少個(gè) then 來處理異步之間可能存在的同步關(guān)系,從以上的代碼可以看到 then 的處理是一層一層的嵌套。如果換成 co,在寫法上更優(yōu)雅也更符合日常同步編程的寫法:
co(function*?()?{try?{var?result1?=?yield?Promise.resolve(1111)//?處理第一個(gè)異步的結(jié)果console.log(result1);//?返回第二個(gè)異步var?result2?=?yield?Promise.resolve(2222)//?處理第二個(gè)異步的結(jié)果console.log(result2)}?catch?(err)?{console.error(err)} });4.2 分析 next 函數(shù)
源碼的 next 函數(shù)接收一個(gè) gen.next() 返回的對(duì)象 ret 作為參數(shù),形如{value: T, done: boolean},next 函數(shù)只有四行代碼。
第一行:if (ret.done) return resolve(ret.value); 如果 ret.done 為 true,表明 gen 函數(shù)到了結(jié)束狀態(tài),就 resolve(ret.value),返回結(jié)果。
第二行:var value = toPromise.call(ctx, ret.value); 調(diào)用 toPromise.call(ctx, ret.value) 函數(shù),toPromise 函數(shù)的作用是把 ret.value 轉(zhuǎn)化成 Promise 類型,也就是用 Promise 包裹一層再 return 出去。
function?toPromise(obj)?{//?如果?obj?不存在,直接返回?objif?(!obj)?return?obj;//?如果?obj?是?Promise?類型,直接返回?objif?(isPromise(obj))?return?obj;//?如果?obj?是生成器函數(shù)或遍歷器對(duì)象,?就遞歸調(diào)用?co?函數(shù)if?(isGeneratorFunction(obj)?||?isGenerator(obj))?return?co.call(this,?obj);//?如果?obj?是普通的函數(shù)類型,轉(zhuǎn)換成?Promise?類型函數(shù)再返回if?('function'?==?typeof?obj)?return?thunkToPromise.call(this,?obj);//?如果?obj?是一個(gè)數(shù)組,?轉(zhuǎn)換成?Promise?數(shù)組再返回if?(Array.isArray(obj))?return?arrayToPromise.call(this,?obj);//?如果?obj?是一個(gè)對(duì)象,?轉(zhuǎn)換成?Promise?對(duì)象再返回if?(isObject(obj))?return?objectToPromise.call(this,?obj);//?其他情況直接返回return?obj; }第三行:if (value && isPromise(value)) return value.then(onFulfilled, onRejected); 如果 value 是 Promise 類型,調(diào)用 onFulfilled 或 onRejected,實(shí)際上是遞歸調(diào)用了 next 函數(shù)本身,直到 done 狀態(tài)為 true 或 throw error。
第四行:return onRejected(...) 如果不是 Promise,直接 Rejected。
5. 實(shí)踐
雖然解讀了 co 的核心代碼,看起來像是懂了,實(shí)際上很容易遺忘。為了加深理解,結(jié)合上面的 co 源碼和自己的思路動(dòng)手實(shí)現(xiàn)一個(gè)簡(jiǎn)略版的 co。
5.1 模擬請(qǐng)求
function?request()?{return?new?Promise((resolve)?=>?{setTimeout(()?=>?{resolve({data:?'request'});},?1000);}); } //?用?yield?獲取?request?的值 function*?getData()?{yield?request() } var?g?=?getData() var?{value,?done}?=?g.next() //?間隔1s后打印?{data:?"request"} value.then(res?=>?console.log(res))5.2 模擬實(shí)現(xiàn)簡(jiǎn)版 co
核心實(shí)現(xiàn):
1)函數(shù)傳參
2)generator.next 自動(dòng)執(zhí)行
function?co(gen)?{//?1.?傳參var?ctx?=?this;const?args?=?Array.prototype.slice.call(arguments,?1);gen?=?gen.apply(ctx,?args);return?new?Promise(function(resolve,?reject)?{//?2.?自動(dòng)執(zhí)行?nextonFulfilled()function?onFulfilled?(res)?{var?ret?=?gen.next(res);next(ret);}function?next(ret){if?(ret.done)?return?resolve(ret.value);//?此處只處理?ret.value?是?Promise?對(duì)象的情況,其他類型簡(jiǎn)略版沒處理var?promise?=?ret.value;//?自動(dòng)執(zhí)行promise?&&?promise.then(onFulfilled);}}) }//?執(zhí)行 co(function*?getData()?{var?result?=?yield?request();//?1s后打印?{data:?"request"}console.log(result) })6. 感想
對(duì)我來說,學(xué)習(xí)一個(gè)新的東西(generator)花費(fèi)的時(shí)間遠(yuǎn)遠(yuǎn)大于單純閱讀源碼的時(shí)間,因?yàn)樾枰私馑a(chǎn)生的背景,語法,解決的問題以及一些應(yīng)用場(chǎng)景,這樣在閱讀源碼的時(shí)候才知道它為什么要這樣寫。
讀完源碼,我們會(huì)發(fā)現(xiàn),其實(shí) co 就是一個(gè)自動(dòng)執(zhí)行 next() 的函數(shù),而且到最后我們會(huì)發(fā)現(xiàn) co 的寫法和我們?nèi)粘J褂玫?async/await 的寫法非常相像,因此也不難理解【async/await 實(shí)際上是對(duì) generator 封裝的一個(gè)語法糖】這句話了。
//?co?寫法 co(function*?getData()?{var?result?=?yield?request();//?1s后打印?{data:?"request"}console.log(result) }) //?async?await?寫法 (async?function?getData()?{var?result?=?await?request();//?1s后打印?{data:?"request"}console.log(result) })()不得不說,閱讀源碼的確是一個(gè)開闊視野的好方法,如果不是這次活動(dòng),我可能還要晚個(gè)大半年才接觸到 generator,接觸協(xié)程的概念,了解到 async/await 實(shí)現(xiàn)的原理,希望能夠繼續(xù)堅(jiān)持下去~
最近組建了一個(gè)江西人的前端交流群,如果你是江西人可以加我微信?ruochuan12?私信 江西?拉你進(jìn)群。
推薦閱讀
1個(gè)月,200+人,一起讀了4周源碼
我讀源碼的經(jīng)歷
老姚淺談:怎么學(xué)JavaScript?
我在阿里招前端,該怎么幫你(可進(jìn)面試群)
·················?若川簡(jiǎn)介?·················
你好,我是若川,畢業(yè)于江西高校。現(xiàn)在是一名前端開發(fā)“工程師”。寫有《學(xué)習(xí)源碼整體架構(gòu)系列》多篇,在知乎、掘金收獲超百萬閱讀。
從2014年起,每年都會(huì)寫一篇年度總結(jié),已經(jīng)寫了7篇,點(diǎn)擊查看年度總結(jié)。
同時(shí),活躍在知乎@若川,掘金@若川。致力于分享前端開發(fā)經(jīng)驗(yàn),愿景:幫助5年內(nèi)前端人走向前列。
識(shí)別上方二維碼加我微信、拉你進(jìn)源碼共讀群
今日話題
略。歡迎分享、收藏、點(diǎn)贊、在看我的公眾號(hào)文章~
總結(jié)
以上是生活随笔為你收集整理的面试官问 async、await 函数原理是在问什么?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 学校计算机学院教学管理ER图,学校课程管
- 下一篇: React开发(124):ant des