JS中Generator函數(shù)與async函數(shù)用法介紹
Generator函數(shù)
Generator 函數(shù)看起來像指針函數(shù),實際上跟我們的普通函數(shù)差不多,有兩個特征
1、function關鍵字與函數(shù)名之間有一個星號
2、函數(shù)體內(nèi)部使用yield表達式,定義不同的內(nèi)部狀態(tài)(yield在英語里的意思就是“產(chǎn)出”),這里有暫停、等待執(zhí)行的意思
Generator函數(shù) 運行后跟普通函數(shù)一樣,只不過碰到 yield 關鍵字會暫停,需要等待調(diào)用next 之后才會繼續(xù)往后執(zhí)行,直到遇到下一個 yield 或 return
yield 作為表達式一項時,需要使用 () 括起來,否則會報錯,例如: 2 * (yield x)
next 標識著 generator 函數(shù)開始往后執(zhí)行語句,默認執(zhí)行到 下一個 yield 語句,并返回 yield 后的內(nèi)容 到 value 中,沒有遇到 return或者函數(shù)結(jié)束,done參數(shù)為false,否則done參數(shù)為 true
return 標識著函數(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 中怎么表示呢,里面是構造函數(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ù),實際上返回的是一個 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é)果,沒有傳參,默認返回都是 undefined
注意: yield 返回的結(jié)果不是該表達式后面的內(nèi)容,且 yield 作為表達式一項時,需要使用 () 括起來,否則會報錯
下面看一下案例就知道結(jié)果了(過程已經(jīng)標出)
//包含表達式時,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, 當做上一個語句的 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. 當做上一個語句的 (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()
// 相當于在 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é)束另外,當存在 try...finally 代碼塊時,并且執(zhí)行到 try 里面時return ,會仍然執(zhí)行finally 里面的語句,且會執(zhí)行到finally里面的 yield語句,finally執(zhí)行完畢,才是真正結(jié)束,并且return帶入的參數(shù),會應用到 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()
}
//相當于下面展開的語句,可以看出yield語句總是一個一個執(zhí)行的,即使嵌套也不會一次執(zhí)行一堆
function* gen3() {
yield 0
yield 1
yield 2
}generator應用
generator應用很多,這里簡單介紹兩種,算是小試牛刀
正常切換開關,需要狀態(tài),這里通過 generator 遍歷器,避免了新增開關狀態(tài)(不適用于多方控制,例如:需要與后臺遠程同步狀態(tài))
//切換狀態(tài)
function* toggleSwitch() {
while(true) {
console.log('開燈')
yield 1
console.log('關燈')
yield 0
}
}
//是不是切換很簡單了
let t = toggleSwitch()
let next = t.next() //開燈
console.log(next)
next = t.next() //關燈
console.log(next)流程化管理,外部不用關心內(nèi)部(例如:加工軟件,不同工種對于自己的操作工序,完成后只需要點擊一下 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ù)改造而成,其改進了 generator函數(shù)作為普通函數(shù)痛點,其加入了執(zhí)行器,能像普通函數(shù)一樣直接執(zhí)行,更加方便,且語義更加清晰,結(jié)果返回一個 Promise(可以看出使用Promise包裝而成)
簡而言之:async 聲明一個異步函數(shù),await 等待執(zhí)行,當await語句執(zhí)行完畢后,才會執(zhí)行后面語句(出現(xiàn)錯誤直接拋出錯誤結(jié)束,不往后執(zhí)行),并且如果沒有 await 的存在,async函數(shù),跟一個普通的同步函數(shù)執(zhí)行順序沒有什么區(qū)別
ps: 關于 promise 前面有介紹,await執(zhí)行的過程與其一樣,實際上會馬上執(zhí)行任務隊列下一個任務,前面的任務執(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í)行,直接反饋錯誤到外部
下面改進上一個案例
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 應用多么方便
function uploadAImage(fileUrl) {
return new Promise(function(resolve, reject) {
setTimeout(() => {
let res = fileUrl + '成功了'
console.log(res)
resolve(res)
}, 500);
})
}
async function uploadImages() {
//準備多個圖片url
let fileUrl = ['url1', 'url2', 'url3']
for (url of fileUrl) {
try {
await uploadAImage(url)
}catch(err) {
console.log(err)
//實際上更復雜,成功的下次就不需要在上傳了
return Promise.reject('存在上傳失敗的內(nèi)容')
}
}
return '都成功了'
}
uploadImages().then(res => {
console.log(res)
}).catch(err => {
console.log(err)
})async、await 函數(shù)使用簡單就先介紹到這里了,后面會對 generator 進行對比,是怎么改造了的
generator 模仿 async 自動執(zhí)行
前面說了 async函數(shù) 是通過 generator函數(shù) 改造而來的,里面添加了執(zhí)行器,我們自己嘗試一下做一個簡易的generator執(zhí)行器
這里是一個async 的默認執(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ù)包裝一下即可,并且將 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í)行
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)
})這樣就實現(xiàn)了一個簡易的 generator 自動執(zhí)行器
同時我們也發(fā)現(xiàn)了,async 與我們普通函數(shù)相比,加劇了負擔,為了優(yōu)化性能,我們平時如果沒有用到異步函數(shù)(或者連 await 都用不到的),將多余的 async 去掉吧,這樣某種程度上能夠避免性能浪費,尤其是到了循環(huán)語句
最后
看到了這里,相信我們也能學習到不少東西,可以想一下,其他語言是不是也是類似這樣呢,遇到了相似的情況我們是不是也豁然開朗了呢,很多語言是互通的,學習就是積累的過程,希望我們此次能有所收獲!
以上就是JS中Generator函數(shù)與async函數(shù)用法介紹的詳細內(nèi)容,更多關于JS Generator函數(shù)與async函數(shù)的資料請關注腳本之家其它相關文章!
相關文章
JavaScript編寫實現(xiàn)飛機大戰(zhàn)
這篇文章主要為大家詳細介紹了JavaScript編寫實現(xiàn)飛機大戰(zhàn),文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-05-05
Javascript必知必會(四)js類型轉(zhuǎn)換
這篇文章主要介紹了Javascript必知必會(四)js類型轉(zhuǎn)換 的相關資料,非常不錯具有參考借鑒價值,需要的朋友可以參考下2016-06-06
require導入module.exports 或 exports導出的使用方法
module.exports用于導出整個模塊的內(nèi)容,可以通過賦值給 module.exports 導出一個對象、函數(shù)或值,導出的內(nèi)容可以被其他模塊通過require 導入,本文給大家介紹require導入module.exports 或 exports導出的使用,感興趣的朋友一起看看吧2023-11-11
利用JS如何計算字符串所占字節(jié)數(shù)示例代碼
因為最近項目有個需求要用js計算一串字符串寫入到localStorage里所占的內(nèi)存,所以便有了這篇文章,下面這篇文章主要給大家介紹了關于利用JS如何計算字符串所占字節(jié)數(shù)的相關資料,需要的朋友可以參考下。2017-09-09

