JS中Generator函數(shù)與async函數(shù)用法介紹
Generator函數(shù)
Generator
函數(shù)看起來像指針函數(shù),實(shí)際上跟我們的普通函數(shù)差不多,有兩個特征
1、function
關(guān)鍵字與函數(shù)名之間有一個星號
2、函數(shù)體內(nèi)部使用yield
表達(dá)式,定義不同的內(nèi)部狀態(tài)(yield
在英語里的意思就是“產(chǎn)出”),這里有暫停、等待執(zhí)行的意思
Generator
函數(shù) 運(yùn)行后跟普通函數(shù)一樣,只不過碰到 yield
關(guān)鍵字會暫停
,需要等待調(diào)用next
之后才會繼續(xù)
往后執(zhí)行,直到遇到下一個 yield
或 return
yield
作為表達(dá)式
一項(xiàng)時,需要使用 ()
括起來,否則會報錯,例如: 2 * (yield x)
next
標(biāo)識著 generator
函數(shù)開始往后執(zhí)行語句,默認(rèn)執(zhí)行到 下一個 yield
語句,并返回 yield 后的內(nèi)容
到 value
中,沒有遇到 return
或者函數(shù)結(jié)束
,done
參數(shù)為false
,否則done
參數(shù)為 true
return
標(biāo)識著函數(shù)的結(jié)束,這里也不例外,只不過結(jié)束時返回的內(nèi)容也可以被 next
返回; 另外,不寫 return
則與return undefined
類似
可以參考下面案例理解
function* generator() { yield 1 yield 2 yield 3 yield 4 return 5 // { value: 5, done: true },再往后 next 就和不寫一樣了 // 不寫return,則與return undefined 一樣 返回 { value: undefined, done: true } } //獲取 generator 遍歷器 let g = generator() let next = null do { next = g.next() //開始往后執(zhí)行到下一個 yield 語句,并返回 console.log(next) }while(next?.done === false) //執(zhí)行到 return 或 函數(shù)結(jié)束 則停止打印 next = g.next() console.log(next) // { value: 1, done: false } // { value: 2, done: false } // { value: 3, done: false } // { value: 4, done: false } // 有return 5 打印 { value: 5, done: true }, 沒有則打印 { value: undefined, done: true }
如果在 class
中怎么表示呢,里面是構(gòu)造函數(shù)的包裝,寫法不一樣, 只需要在前面加上一個*
即可
class A { //前面加上 * 即可 * generator() { yield 1 yield 2 } } let a = new A() let g = a.generator() let next = g.next() console.log(next) next = g.next() console.log(next)
generator 與 Iterator遍歷器
從上面打印就可以看出,generator函數(shù)
,實(shí)際上返回的是一個 Iterator遍歷器
,因此我們亦可以通過遍歷器的手段來執(zhí)行 generator遍歷器
下面使用 for ... of
來遍歷一下我們上面的 generator遍歷器
//從上面就可以看出,generator函數(shù) 返回了一個 Iterator遍歷器對象 //我們遍歷一次試試 let g = generator() for (let item of g) { console.log(item) } //結(jié)果打印 1 2 3 4
看其結(jié)果發(fā)現(xiàn)沒有打印最后的return
,了解遍歷器就會知道,遍歷器遇到 done
為true
時會直接結(jié)束(作為遍歷器使用的話,不要在 return 語句
放遍歷內(nèi)容)
我們再對比一下看看我們的 generator遍歷器
是否真的是 Iteraotr遍歷器
,發(fā)現(xiàn)一模一樣
//g[Symbol.iterator]() === g //遍歷器屬性一致,返回為 true,不信打印遍歷器屬性比較試試
next
前面介紹了,generator遍歷器
調(diào)用 next()
,會繼續(xù)執(zhí)行到下一個 yield語句
此外,next
可以傳參
,我們傳遞的參數(shù)
會作為 上一個 yield 返回的結(jié)果
,沒有傳參,默認(rèn)返回都是 undefined
注意: yield
返回的結(jié)果不是
該表達(dá)式后面的內(nèi)容,且 yield 作為表達(dá)式一項(xiàng)時,需要使用 () 括起來,否則會報錯
下面看一下案例就知道結(jié)果了(過程已經(jīng)標(biāo)出)
//包含表達(dá)式時,yield 語句需要用括號括起來,否則會報錯 function* generator() { let num1 = yield 1 console.log('num1', num1) let num2 = num1 * (yield 2) console.log('num2', num2) yield 3 yield 4 } //next傳入的值,作為上一個 yield 的返回值使用 let g = generator() let next = g.next() //此時執(zhí)行到 yield 1,并包裝返回 yield 1 執(zhí)行的結(jié)果 console.log(next) //{ value: 1, done: false } //傳入 2, 當(dāng)做上一個語句的 yield 1 的返回值(不傳接收到的值均為undefined) //即:返回 2 賦值給 num,然后執(zhí)行到 (yield 2),并包裝返回 (yield 2) 的結(jié)果 next = g.next(2) //執(zhí)行完畢后,打?。簄um1 2 console.log(next) //{ value: 2, done: false } //傳入 3. 當(dāng)做上一個語句的 (yield 2) 的返回值(不傳接收到的值均為undefined) //即:num2 = num1 * 3, 然后執(zhí)行到 yield 3,并包裝返回 yield 3 的結(jié)果 next = g.next(3) //執(zhí)行完畢后,打印:num2 6 console.log(next) //{ value: 3, done: false }
由上述可以可以看到 next 傳參
挺好用的,除了上述,甚至可以使用傳遞的參數(shù),用于跳出死循環(huán)......
throw
這里的throw
為通過 generator
遍歷器拋出的異常,其異常會拋出到 generator 函數(shù)里面
,因此需要注意拋出異常的位置
,才能更好地配合try...catch
使用
//throw() // 相當(dāng)于在 generator 里面拋出了一個異常 function* generator() { try { yield 1 }catch(err) { console.log(err) } yield 2 yield 3 yield 4 } let g = generator() let next = g.next() console.log(next) //執(zhí)行完畢 yield 1后,隨后在 yield 1 ~ yield 2 之間拋出異常,因此,try...catch 包裹主 yield 1 才行 let t = g.throw('啦啦啦') // 拋出異常并執(zhí)行到下一個 yield,如果沒處理錯誤,程序異常 console.log(t) next = g.next() console.log(next)
return
指定 return
會理立即結(jié)束 generator 函數(shù)
,并返回 { value: undefined, done: true }
,上面有介紹
function* generator() { yield 1 yield 2 yield 3 yield 4 } let g = generator() next = g.next() next = g.return() //直接返回 { value: undefined, done: true },如果傳參則返回return傳遞的參數(shù) console.log(next) //會直接結(jié)束
另外,當(dāng)存在 try...finally
代碼塊時,并且執(zhí)行到 try
里面時return
,會仍然執(zhí)行finally
里面的語句,且會執(zhí)行到finally里面
的 yield
語句,finally
執(zhí)行完畢,才是真正結(jié)束,并且return
帶入的參數(shù),會應(yīng)用到 finally
語句的末尾
function* generator() { yield 1 try { //執(zhí)行到這里,外面 return 的話,也需要走完 finally 才會結(jié)束 yield 2 yield 3 }finally { yield 7 yield 8 } yield 4 } let g1 = generator() next = g1.next() // { value: 1, done: false } next = g1.next() // { value: 2, done: false } next = g1.return(100) // { value: 7, done: false },return 后返回下一個,為finally中的 yield 7,這里順道返回一個值測試一下最后的返回值 next = g1.next() // { value: 8, done: false } next = g1.next() // { value: 100, done: true } next = g1.next() // { value: undefined, done: true }
yield*
yield*
看著像個指針,其指向一個 generator遍歷器
,會自動展開,不多說
//yield* 會展開 generator 函數(shù) function* gen1() { yield 1 yield 2 } function* gen2() { yield 0 yield* gen1() } //相當(dāng)于下面展開的語句,可以看出yield語句總是一個一個執(zhí)行的,即使嵌套也不會一次執(zhí)行一堆 function* gen3() { yield 0 yield 1 yield 2 }
generator應(yīng)用
generator
應(yīng)用很多,這里簡單介紹兩種,算是小試牛刀
正常切換開關(guān),需要狀態(tài),這里通過 generator 遍歷器,避免了新增開關(guān)狀態(tài)(不適用于多方控制,例如:需要與后臺遠(yuǎn)程同步狀態(tài))
//切換狀態(tài) function* toggleSwitch() { while(true) { console.log('開燈') yield 1 console.log('關(guān)燈') yield 0 } } //是不是切換很簡單了 let t = toggleSwitch() let next = t.next() //開燈 console.log(next) next = t.next() //關(guān)燈 console.log(next)
流程化管理,外部不用關(guān)心內(nèi)部(例如:加工軟件,不同工種對于自己的操作工序,完成后只需要點(diǎn)擊一下 next 即可)
function* order() { yield '收到訂單' yield '加工' yield '送檢' yield '質(zhì)檢' yield '發(fā)貨' yield '完成訂單' } let o = order() let next = o.next() //收到訂單 next = o.next() //加工 next = o.next() //送檢 next = o.next() //質(zhì)檢 next = o.next() //發(fā)貨 next = o.next() //完成訂單
async、await函數(shù)
async、await
,我們平時用的比較多,這里簡單介紹一下
async、await
就是根據(jù) generator函數(shù)
改造而成,其改進(jìn)了 generator函數(shù)
作為普通函數(shù)痛點(diǎn),其加入了執(zhí)行器
,能像普通函數(shù)一樣直接執(zhí)行,更加方便
,且語義更加清晰
,結(jié)果返回一個 Promise
(可以看出使用Promise包裝而成)
簡而言之:async
聲明一個異步函數(shù),await
等待執(zhí)行,當(dāng)await
語句執(zhí)行完畢后,才會執(zhí)行后面語句(出現(xiàn)錯誤直接拋出錯誤結(jié)束,不往后執(zhí)行),并且如果沒有 await
的存在,async
函數(shù),跟一個普通的同步函數(shù)執(zhí)行順序沒有什么區(qū)別
ps: 關(guān)于 promise
前面有介紹,await
執(zhí)行的過程與其一樣,實(shí)際上會馬上執(zhí)行任務(wù)隊(duì)列下一個任務(wù),前面的任務(wù)執(zhí)行完畢后,才會執(zhí)行到 await 后面的語句
簡單做一個使用案例,
async function a() { await promise1... await promise2... // 如果不return自己內(nèi)容,返回的則是 undefind return 1 } function cc() { a().then(res => { console.log(res) }).catch(err => { console.log(err) }) }
你可能會想,如果第一個 promise 出錯了怎么辦,第二個會執(zhí)行么?
答案:不會,第一個出錯后,會拋出一個異常,此時會結(jié)束解釋器的執(zhí)行,直接反饋錯誤到外部
下面改進(jìn)上一個案例
function a1() { return Promise.reject('出錯了 ') } function a2() { return Promise.resolve('成功了 ') } async function a() { await a1() console.log(11111111111) await a2() // 如果不return自己內(nèi)容,返回的則是 undefind return 1 } function cc() { a().then(res => { console.log(res) }).catch(err => { console.log(err) }) } //結(jié)果是沒有執(zhí)行打印 11111111111,且執(zhí)行到了 .catch 中
下面我們模擬傳遞多個圖片的案例,可以看出 async、await
應(yīng)用多么方便
function uploadAImage(fileUrl) { return new Promise(function(resolve, reject) { setTimeout(() => { let res = fileUrl + '成功了' console.log(res) resolve(res) }, 500); }) } async function uploadImages() { //準(zhǔn)備多個圖片url let fileUrl = ['url1', 'url2', 'url3'] for (url of fileUrl) { try { await uploadAImage(url) }catch(err) { console.log(err) //實(shí)際上更復(fù)雜,成功的下次就不需要在上傳了 return Promise.reject('存在上傳失敗的內(nèi)容') } } return '都成功了' } uploadImages().then(res => { console.log(res) }).catch(err => { console.log(err) })
async、await 函數(shù)使用簡單就先介紹到這里了,后面會對 generator 進(jìn)行對比,是怎么改造了的
generator 模仿 async 自動執(zhí)行
前面說了 async
函數(shù) 是通過 generator
函數(shù) 改造而來的,里面添加了執(zhí)行器,我們自己嘗試一下做一個簡易的generator
執(zhí)行器
這里是一個async 的默認(rèn)執(zhí)行案例
//使用 generator + 執(zhí)行器,簡單翻譯一下 async、await async function asyncDefaultFunction() { let res = await new Promise((resolve) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('啦啦啦') // }, 1000); }) return res } asyncDefaultFunction().then(res => { console.log(res) })
我們使用 generator
改造成 async
自動執(zhí)行的模式
//使用 async 之后,實(shí)際上我們將返回函數(shù)包裝一下即可,并且將 yield 就是 await function asyncFunction() { return co(function* () { //async 函數(shù)里面的內(nèi)容,將 yield 與 await 替換即可 let res = yield new Promise((resolve, reject) => { setTimeout(() => { resolve(1) }, 1000); // setTimeout(() => { // reject('啦啦啦') // }, 1000); }) return res }) } //編寫一個自動執(zhí)行 generator 的 co 函數(shù) function co(genFunc) { return new Promise(function (resolve, reject) { const g = genFunc() //獲取generator,準(zhǔn)備執(zhí)行 function nextFunc(value) { let next; try { next = g.next(value) }catch(err) { return reject(err) } if (next.done) { return resolve(next.value) } //為什么要promise包裝,語句可能為同步函數(shù),可能為異步函數(shù),木事保證正確執(zhí)行 //如果為 promise 直接原封不動返回,如果為普通對象則包裝,具體見promise Promise.resolve(next.value).then(function(res) { nextFunc(res) }, function(err) { reject(err) }) } nextFunc(undefined) }) } asyncFunction().then(res => { console.log(res) }).catch(err => { console.log(err) })
這樣就實(shí)現(xiàn)了一個簡易的 generator 自動執(zhí)行器
同時我們也發(fā)現(xiàn)了,async
與我們普通函數(shù)相比,加劇了負(fù)擔(dān)
,為了優(yōu)化性能,我們平時如果沒有用到異步函數(shù)(或者連 await
都用不到的),將多余的 async
去掉吧,這樣某種程度上能夠避免性能浪費(fèi),尤其是到了循環(huán)語句
最后
看到了這里,相信我們也能學(xué)習(xí)到不少東西,可以想一下,其他語言是不是也是類似這樣呢,遇到了相似的情況我們是不是也豁然開朗了呢,很多語言是互通的,學(xué)習(xí)就是積累的過程,希望我們此次能有所收獲!
以上就是JS中Generator函數(shù)與async函數(shù)用法介紹的詳細(xì)內(nèi)容,更多關(guān)于JS Generator函數(shù)與async函數(shù)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS動態(tài)遍歷json中所有鍵值對的方法(不知道屬性名的情況)
這篇文章主要介紹了JS動態(tài)遍歷json中所有鍵值對的方法,實(shí)例分析了針對不知道屬性名的情況簡單遍歷json鍵值對的操作技巧,需要的朋友可以參考下2016-12-12onbeforeunload與onunload事件異同點(diǎn)總結(jié)
本文對onbeforeunload與onunload事件的異同點(diǎn)、觸發(fā)于、可以用在哪些元素以及解決刷新頁面時不調(diào)用onbeforeunload等等,感興趣的朋友可以參考下哈2013-06-06bootstrap中模態(tài)框、模態(tài)框的屬性實(shí)例詳解
這篇文章主要介紹了bootstrap中模態(tài)框、模態(tài)框的屬性實(shí)例詳解,非常不錯,具有參考借鑒價值,需要的朋友可以參考下2017-02-02JavaScript+CSS實(shí)現(xiàn)仿天貓側(cè)邊網(wǎng)頁菜單效果
這篇文章主要介紹了JavaScript+CSS實(shí)現(xiàn)仿天貓側(cè)邊網(wǎng)頁菜單效果,涉及javascript鼠標(biāo)事件及頁面元素動態(tài)操作的實(shí)現(xiàn)技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-08-08ES6的內(nèi)置對象擴(kuò)展實(shí)現(xiàn)示例
本文主要介紹了ES6的內(nèi)置對象擴(kuò)展實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07