JavaScript中async與await實現(xiàn)原理與細(xì)節(jié)
一、回調(diào)地獄
在es6興起之后許多人都開始使用promise,promise目的是解決es5中的回調(diào)地獄(callback hell),那么什么是回調(diào)地獄呢?先來提一個需求,現(xiàn)在需要發(fā)送n個request請求,第二個請求參數(shù)需要第一個請求的結(jié)果,第三個請求的參數(shù)需要第二個請求的結(jié)果,以此類推... ,在沒有promise的條件下,我們不難使用callback寫出如下的代碼:
function ajax(url, callback) { setTimeout(() => { callback(Math.random() + url) }, 1000); } function request() { ajax('url1', (res1 => { ajax(`url2?random=${res1}`, (res2) => { ajax(`url3?random=${res2}`, (res3) => { ajax(`url4?random=${res3}`, (res4) => { // do something }) }) }) })) } request()
二、Promise
這樣確實能實現(xiàn)我們的需求,但是這樣子的代碼有什么缺點呢?不難看出我們的request函數(shù)越來越像個三角形 ,代碼集中在上部分,下半部分全都是我們的括號,代碼閱讀性極差! 這時候我們的promise應(yīng)運而生了,使用promise我們可以這樣重構(gòu)我們的代碼如下:
function ajax(url) { return new Promise(resolve => { setTimeout(() => { resolve(Math.random() + url) }, 1000); }) } function request() { ajax('url1').then(res1 => { ajax(`url2?random=${res1}`).then((res2) => { ajax(`url3?random=${res2}`).then((res3) => { ajax(`url4?random=${res3}`).then((res4) => { // do something }) }) }) }) } request()
肯定有人說,這不還是像個三角形嗎?這樣使用promise有什么意義呢?此時我們可以借助promise的鏈?zhǔn)秸{(diào)用重構(gòu)成下面這樣:
function ajax(url) { return new Promise(resolve => { setTimeout(() => { resolve(Math.random() + url) }, 1000); }) } function request() { ajax('url1').then(res1 => { return ajax(`url2?random=${res1}`) }).then(res2 => { return ajax(`url3?random=${res2}`) }).then(res3 => { return ajax(`url4?random=${res3}`) }).then(res4 => { // do something }) } request()
相對于之前的回調(diào)地獄,此時我們的代碼是不是比較清晰了。但是!這還不夠!這看上去還不夠直觀。我們想要的是閱讀異步代碼,類似于閱讀同步代碼的方式一樣方便。
三、生成器(generator)
生成器是es6新增的語法,它是一個特殊的迭代器,它可以用來暫停我們函數(shù)的執(zhí)行!這個功能非常強大! 生成器的語法是,在聲明函數(shù)時在后面增加一個 * 號,那么這個函數(shù)就是生成器函數(shù),直接調(diào)用該函數(shù)得到的是一個生成器句柄,該生成器是不會執(zhí)行的,必須要調(diào)用生成器句柄的next()方法后,生成器才會執(zhí)行,并且執(zhí)行到我們的yield處(如果存在yield就執(zhí)行到第一個yield,不存在則直接執(zhí)行完畢),該方法的返回值一個對象,結(jié)構(gòu)是 {done: true/false, value: 我們yield后面跟的值} ,如果執(zhí)行到該生成器函數(shù)末尾則 done為true。 關(guān)于生成器的知識可以點此JavaScript中的迭代器和可迭代對象與生成器
function* foo() { console.log('======'); const a = yield 1; console.log('a',a); } const g = foo() console.log('11111111') const res1 = g.next() console.log(res1) const res2 = g.next('22222') console.log(res2)
上面代碼打印順序為:
11111111
======
{done: false, value: 1}
'a','22222'
{done: true, value: undefined}
細(xì)心的你一定看出了,我們在next方法中傳的參數(shù)會賦值給生成器函數(shù)中的yield 左側(cè),并可以在生成器中拿到這個值后進行使用。
四、使用生成器同步化promise
掌握了生成器的知識我們就可以使用生成器來將我們的promise鏈?zhǔn)秸{(diào)用進行重構(gòu)如下:
function ajax(url) { return new Promise(resolve => { setTimeout(() => { resolve(Math.random() + url) }, 1000); }) } function* request() { const res1 = yield ajax('url1') const res2 = yield ajax(`url2?random=${res1}`) const res3 = yield ajax(`url3?random=${res2}`) const res4 = yield ajax(`url4?random=${res3}`) // do something console.log(res4); } // 開始調(diào)用我們的生成器 const generator = request(); generator.next().value.then(res1 => { generator.next(res1).value.then(res2 => { generator.next(res2).value.then(res3 => { generator.next(res3).value.then(res4 => { generator.next(res4) }) }) }) })
可以看到我們的生成器還是三角形,優(yōu)化一下成鏈?zhǔn)秸{(diào)用如下:
generator.next().value.then(res1 => { return generator.next(res1).value }).then(res2 => { return generator.next(res2).value }).then(res3 => { return generator.next(res3).value }).then(res4 => { generator.next(res4) })
此時,我們的主函數(shù)已經(jīng)非常像同步代碼了,但是缺點是我們目前必須手動調(diào)用該生成器,并且request主函數(shù)里面我們不知道有多少次yield調(diào)用,因此我們的生成器也不能手動調(diào)用多次,這時,我們將該生成器調(diào)用代碼進行重構(gòu),重構(gòu)成可以自動執(zhí)行我們的生成器的函數(shù),不需要關(guān)心request內(nèi)部有多少次yield使用,重構(gòu)如下:
function ajax(url) { return new Promise(resolve => { setTimeout(() => { resolve(Math.random() + url) }, 1000); }) } function* request() { const res1 = yield ajax('url1') const res2 = yield ajax(`url2?random=${res1}`) const res3 = yield ajax(`url3?random=${res2}`) const res4 = yield ajax(`url4?random=${res3}`) // do something console.log(res4); } function execGenerator(generatorFn) { const generator = generatorFn(); function exec(res) { const { done, value } = generator.next(res) if (!done) { value.then(exec) } } exec() } execGenerator(request)
我們增加了一個自動執(zhí)行函數(shù)execGenerator,該函數(shù)接受一個生成器參數(shù),并且在內(nèi)部自動進行遞歸調(diào)用,直至返回值的 done 屬性為 true,此時我們的使用方式只需要定義一個request生成器函數(shù),并且執(zhí)行一下我們的自動執(zhí)行函數(shù) execGenerator ,我們的request就能像同步代碼一樣盤跑起來了,并且看起來非常直觀。
五、async、await異步代碼究極解決方案
其實async與await是我們上面生成器的語法糖而已,在內(nèi)部做的事情跟我們使用生成器做的事情是一樣的,使用方式如下:
function ajax(url) { return new Promise(resolve => { setTimeout(() => { resolve(Math.random() + url) }, 1000); }) } async function request() { const res1 = await ajax('url1') const res2 = await ajax(`url2?random=${res1}`) const res3 = await ajax(`url3?random=${res2}`) const res4 = await ajax(`url4?random=${res3}`) // do something console.log(res4); }
看起來是不是跟我們的生成器request函數(shù)非常類似呢?使用async與await可以讓我們省去寫execGenerator函數(shù)的步驟,更加方便了我們的開發(fā)!
到此這篇關(guān)于JavaScript中async與await實現(xiàn)原理與細(xì)節(jié)的文章就介紹到這了,更多相關(guān)JS 中async與await 內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
JavaScript中判斷整數(shù)的多種方法總結(jié)
這篇文章主要介紹了JavaScript中判斷整數(shù)的多種方法總結(jié),本文總結(jié)了5種判斷整數(shù)的方法,如取余運算符判斷、Math.round、Math.ceil、Math.floor判斷等,需要的朋友可以參考下2014-11-11JS獲取下拉列表所選中的TEXT和Value的實現(xiàn)代碼
本篇文章主要是對JS獲取下拉列表所選中的TEXT和Value的實現(xiàn)代碼進行了介紹,需要的朋友可以過來參考下,希望對大家有所幫助2014-01-01