JavaScript Generator函數(shù)使用分析
1. Generator的定義和執(zhí)行
如果說 Promise 是為了解決回調(diào)地獄的難題出現(xiàn)的,那么 Generator 就是為了解決異步問題而出現(xiàn)的。
普通函數(shù),如果調(diào)用它會立即執(zhí)行完畢;Generator 函數(shù),它可以暫停,不一定馬上把函數(shù)體中的所有代碼執(zhí)行完畢,正是因為有這樣的特性,它可以用來解決異步問題。
定義一個 Generator 函數(shù),定義的方式和定義一個普通函數(shù)是類似的,不同之處在于它在 function 和函數(shù)名之間有一個*號。
Generator 函數(shù)返回是一個迭代器對象,需要通過 xx.next 方法來完成代碼執(zhí)行。在調(diào)用 generator 函數(shù)時,它只是進行實例化工作,它沒有讓函數(shù)體里面的代碼執(zhí)行,需要通過 next 方法來讓它執(zhí)行,比如像下面這樣:
function* gen() {
console.log(1)
}
// 定義迭代器對象
const iterator = gen()
iterator.next() // 如果不執(zhí)行這一局代碼,1不會被打印當 next 方法執(zhí)行時遇到了 yield 就會停止,直到你再次調(diào)用 next 方法。比如像下面這樣:
function* gen() {
yield 1
console.log('A')
yield 2
console.log('B')
yield 3
console.log('C')
return 4
}
// 定義迭代器對象
const iterator = gen()
iterator.next() // 執(zhí)行 gen 函數(shù),打印為空,遇到 yield 1 停止執(zhí)行
iterator.next() // 繼續(xù)執(zhí)行函數(shù),打印 A,遇到 yield 2 停止執(zhí)行
iterator.next() // 繼續(xù)執(zhí)行函數(shù),打印 B,遇到 yield 3 停止執(zhí)行
iterator.next() // 繼續(xù)執(zhí)行函數(shù),打印 C
next 方法調(diào)用時,它是有返回值的,它的返回值就是 yield 后面的值或函數(shù)的返回值。比如下面這個例子:
// 同步代碼
function* gen() {
yield 1
console.log('A')
yield 2
console.log('B')
yield 3
console.log('C')
return 4
}
// 定義迭代器對象
const iterator = gen()
// 異步代碼
console.log(iterator.next()) // 打印為空 next返回 {value:1,done:false}
console.log(iterator.next()) // A next返回 {value:2,done:false}
console.log(iterator.next()) // B next返回 {value:3,done:false}
console.log(iterator.next()) // C next返回 {value:4,done:true},如果函數(shù)有return值,最后一個next方法,它的value值為return的值 value:4;如果沒有。值為 undefined
拓展:其實之所以我們說 Generator 能夠把異步變同步,是因為 Generator 函數(shù)中我們只需要寫同步代碼就可以,真正執(zhí)行異步操作的是迭代器對象。在復雜的業(yè)務邏輯中,大量使用迭代器對象來執(zhí)行異步操作,會使得代碼變得很不優(yōu)雅,于是 ES7 中就推出了 async await 方案來實現(xiàn)異步變同步。在 async await 方案中可以只書寫同步代碼,真正的異步操作被封裝在底層,這樣的寫法,使得代碼變優(yōu)雅了很多。
2. Generator中yield在賦值號左邊的情況
yield 在等號右邊時,它的返回值并不會返回給等號左邊的變量,依然會返回給 next 方法。
function* gen(num) {
let r1 = yield 1
console.log('r1', r1);
let r2 = yield 2
console.log('r2', r2);
let r3 = yield 3
console.log('r3', r3);
}
const iterator = gen()
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next())
console.log(iterator.next()) 
這是因為 generator 函數(shù)在遇到 yield 時就已經(jīng)暫停執(zhí)行了,并不會執(zhí)行到賦值操作,直到在執(zhí)行完 next 方法之后,才會繼續(xù)向下執(zhí)行賦值操作。如果我們想要 r1/r2/r3 有值,我們可以用 next 方法進行傳值。就像下面這樣:
function* gen(num) {
let r1 = yield 1
console.log('r1', r1);
let r2 = yield 2
console.log('r2', r2);
let r3 = yield 3
console.log('r3', r3);
}
const iterator = gen()
iterator.next() // 第一個 next 方法不用給值,即使給值也不會生效
iterator.next('A')
iterator.next("B")
iterator.next('C')
3. Generator函數(shù)嵌套使用
function* gen1() {
yield 1
yield 2
}
function* gen2() {
yield 3
// generator函數(shù)的嵌套
// 這種寫法對應 方案1
// yield gen1()
yield* gen1()
yield 4
}
const iterator = gen2()
console.log(iterator.next()); // {value:3,done:false}
// 如果我們想執(zhí)行到 gen1 中的 yield 值
// console.log(iterator.next()); // {value:generator實例,done:false}
// let itor = iterator.next().value
// console.log(itor.next()); // {value:1,done:false}
// console.log(itor.next()); // {value:2,done:false}
// 方案2
console.log(iterator.next()); // {value:1,done:false} 你需要在yield后面加一個*,讓它知道后面是一個generator對象
console.log(iterator.next()); // {value:2,done:false}
console.log(iterator.next()); // {value:4,done:false}
console.log(iterator.next()); // {value:undefined,done:true}
4. 使用generator函數(shù)完成網(wǎng)絡請求
// 使用generator來完成異步網(wǎng)絡請求,它還是要利用到promise
// 模擬網(wǎng)絡請求
function request(num = 1) {
return new Promise((resolve, reject) => {
return setTimeout(() => {
resolve(++num)
}, 1000);
})
}
// generator函數(shù)中的代碼,發(fā)起的網(wǎng)絡請求它就類似于同步寫法
function* gen(num) {
// yield右側(cè)是一個promise對象
let r1 = yield request(10)
console.log('r1', r1);
let r2 = yield request(r1)
console.log('r2', r2);
let r3 = yield request(r2)
console.log('r3', r3);
}
const iterator = gen(10)
iterator.next().value.then(ret1 => {
iterator.next(ret1).value.then(ret2 => {
iterator.next(ret2).value.then(ret3 => {
iterator.next(ret3)
})
})
})
上面的寫法不夠優(yōu)雅,當有多個網(wǎng)絡請求時,異步操作部分的代碼會變得非常復雜,所以我們可以通過 co 庫中的迭代函數(shù)來改寫一下:
// 使用generator來完成異步網(wǎng)絡請求,它還是要利用到promise
// 模擬網(wǎng)絡請求
function request(num) {
return new Promise((resolve, reject) => {
return setTimeout(() => {
resolve(++num)
}, 1000);
})
}
// generator函數(shù)中的代碼,發(fā)起的網(wǎng)絡請求它就類似于同步寫法
function* gen(num) {
// yield右側(cè)是一個promise對象
let r1 = yield request(10)
console.log('r1', r1);
let r2 = yield request(r1)
console.log('r2', r2);
let r3 = yield request(r2)
console.log('r3', r3);
let r4 = yield request(r3)
console.log('r4', r4);
let r5 = yield 'ok'
console.log('r5', r5);
}
// 通過co庫實現(xiàn)
function co(generator, ...params) {
const iterator = gen(...params)
// 迭代函數(shù)
const next = n => {
let { value, done } = iterator.next(n)
// 判斷一下value它是一個promise對象,如果不是promise對象則需要手動轉(zhuǎn)為promise對象,或拋異常
if (value != undefined && typeof value.then != "function") {
throw new Error('必須為promise對象')
// value = Promise.resolve(value)
}
if (done) return;
// value.then(ret => next(ret))
value.then(next)
}
next(0)
}
co(gen, 100)
到此這篇關于JavaScript Generator函數(shù)使用分析的文章就介紹到這了,更多相關JS Generator內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Postman如何實現(xiàn)參數(shù)化執(zhí)行及斷言處理
這篇文章主要介紹了Postman如何實現(xiàn)參數(shù)化執(zhí)行及斷言處理,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2020-07-07
微信小程序?qū)崙?zhàn)之自定義模態(tài)彈窗(8)
這篇文章主要為大家詳細介紹了微信小程序?qū)崙?zhàn)之自定義模態(tài)彈窗,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-04-04
javascript另類方法實現(xiàn)htmlencode()與htmldecode()函數(shù)實例分析
這篇文章主要介紹了javascript另類方法實現(xiàn)htmlencode()與htmldecode()函數(shù),結合實例形式分析了javascript字符編碼與解碼操作的相關技巧,需要的朋友可以參考下2016-11-11

