異步無(wú)處不在:終極解決方案(四)
(????)??嗨,我是你穩(wěn)定更新、持續(xù)輸出的勾勾。

異步無(wú)處不在:Promise 破除“回調(diào)地獄”(三)
前三篇內(nèi)容,讓我們認(rèn)識(shí)了 JS 世界中的同步和異步模式,也清楚了回調(diào)地獄的產(chǎn)生和破解。
回憶一下第三篇的結(jié)尾:
雖然我們脫離了回調(diào)地獄,但是 .then 的鏈?zhǔn)秸{(diào)用依然不太友好。
頻繁的 .then 并不符合自然的運(yùn)行邏輯,Promise 的寫(xiě)法只是回調(diào)函數(shù)的改進(jìn),使用then 方法以后,異步任務(wù)的兩段執(zhí)行看得更清楚了,除此以外,并無(wú)新意。
Promise 的最大問(wèn)題是代碼冗余。原來(lái)的任務(wù)被 Promise 包裝了一下,不管什么操作,一眼看去都是一堆 then,原來(lái)的語(yǔ)義變得很不清楚。?
于是,在 Promise 的基礎(chǔ)上,Async 函數(shù)出現(xiàn)了。
終極異步解決方案,
千呼萬(wàn)喚地在 ES2017 中發(fā)布了。
Async/Await 語(yǔ)法糖
Async 函數(shù)使用起來(lái),也是很簡(jiǎn)單。
將調(diào)用異步的邏輯全部寫(xiě)進(jìn)一個(gè)函數(shù)中,函數(shù)前面使用 async 關(guān)鍵字。
在函數(shù)中異步調(diào)用邏輯的前面使用 await ,異步調(diào)用會(huì)在 await 的地方等待結(jié)果,然后進(jìn)入下一行代碼的執(zhí)行,這就保證了代碼的后續(xù)邏輯,可以等待異步的 ajax 調(diào)用結(jié)果了。
而代碼看起來(lái)的執(zhí)行邏輯,和同步代碼幾乎一樣。
async function callAjax(){var a = await myAjax('./d1.json')console.log(a);var b = await myAjax('./d2.json');console.log(b)var c = await myAjax('./d3.json');console.log(c)}callAjax();
注意:await 關(guān)鍵詞只能在 async 函數(shù)內(nèi)部使用。
因?yàn)槭褂煤?jiǎn)單,很多人也不會(huì)探究其使用的原理,無(wú)非就是兩個(gè)單詞加到前面用就好了。雖然會(huì)用,日常開(kāi)發(fā)看起來(lái)也沒(méi)什么問(wèn)題,但是一遇到 Bug 調(diào)試,就只能涼涼。面試的時(shí)候也總是知其然不知其所以然。
之前也寫(xiě)過(guò)一篇 BAT 的異步面試真題:字節(jié)百度前端面試真題:異步處理方案(內(nèi)附答案)。有興趣的小伙伴可以再次回顧下。
咱們先來(lái)一個(gè)面試題試試,你能運(yùn)行出正確的結(jié)果嗎?
async 面試題
請(qǐng)寫(xiě)出以下代碼的運(yùn)行結(jié)果:
setTimeout(function () {console.log('setTimeout')}, 0)async function async1() {console.log('async1 start')await async2();console.log('async1 end')}async function async2() {console.log('async2')}console.log('script start')async1();console.log('script end')
答案我放在最后面,你也可以自己寫(xiě)出來(lái)運(yùn)行一下。
想要把結(jié)果搞清楚,我們需要引入另一個(gè)內(nèi)容:Generator 生成器函數(shù)。
先看一段代碼:
function * foo(){console.log('test');// 暫停執(zhí)行并向外返回值yield 'yyy'; // 調(diào)用 next 后,返回對(duì)象值console.log(33);}// 調(diào)用函數(shù) 不會(huì)立即執(zhí)行,返回 生成器對(duì)象const generator = foo();// 調(diào)用 next 方法,才會(huì) *開(kāi)始* 執(zhí)行// 返回 包含 yield 內(nèi)容的對(duì)象const yieldData = generator.next();console.log(yieldData) //=> {value: "yyy", done: false}// 對(duì)象中 done ,表示生成器是否已經(jīng)執(zhí)行完畢// 函數(shù)中的代碼并沒(méi)有執(zhí)行結(jié)束// 下一次的 next 方法調(diào)用,會(huì)從前面函數(shù)的 yeild 后的代碼開(kāi)始執(zhí)行console.log(generator.next()); //=> {value: undefined, done: true}
你會(huì)發(fā)現(xiàn),在函數(shù)聲明的地方,函數(shù)名前面多了 * 星號(hào),函數(shù)體中的代碼有個(gè) yield ,用于函數(shù)執(zhí)行的暫停。
簡(jiǎn)單點(diǎn)說(shuō)就是,這個(gè)函數(shù)不是個(gè)普通函數(shù),調(diào)用后不會(huì)立即執(zhí)行全部代碼,而是在執(zhí)行到 yield 的地方暫停函數(shù)的執(zhí)行,并給調(diào)用者返回一個(gè)遍歷器對(duì)象。
yield 后面的數(shù)據(jù),就是遍歷器對(duì)象的 value 屬性值。
如果要繼續(xù)執(zhí)行后面的代碼,需要使用遍歷器對(duì)象中的 next() 方法,代碼會(huì)從上一次暫停的地方繼續(xù)往下執(zhí)行。
是不是 so easy!
同時(shí),在調(diào)用 next 的時(shí)候,還可以傳遞參數(shù),函數(shù)中上一次停止的 yeild 就會(huì)接受到當(dāng)前傳入的參數(shù)。
function * foo(){console.log('test');// 下次 next 調(diào)用傳參接受const res = yield 'yyy';console.log(res);}const generator = foo();// next 傳值const yieldData = generator.next();console.log(yieldData)// 下次 next 調(diào)用傳參,可以在 yield 接受返回值generator.next('test123');
Generator 的最大特點(diǎn)就是讓函數(shù)的運(yùn)行可以暫停。
不要小看他,有了這個(gè)暫停,我們能做的事情就太多了。在調(diào)用異步代碼時(shí),就可以先 yield 停一下,停下來(lái)我們就可以等待異步的結(jié)果了。
那么如何把 Generator 寫(xiě)到異步中呢?
明天見(jiàn)(? ?_?)?。
推薦閱讀:
異步無(wú)處不在:Promise 破除“回調(diào)地獄”(三)
技術(shù)人年度總結(jié) | 2020,注定不平凡
2020 最后一篇技術(shù)文:可愛(ài)的烏咪 UmiJS
前端人因?yàn)?Vue3 的 Ref-sugar 提案打起來(lái)了!
點(diǎn)點(diǎn)“贊”和“在看”,保護(hù)頭發(fā),減少bug。
