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)
到此這篇關(guān)于JavaScript Generator函數(shù)使用分析的文章就介紹到這了,更多相關(guān)JS Generator內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
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-04javascript另類方法實現(xiàn)htmlencode()與htmldecode()函數(shù)實例分析
這篇文章主要介紹了javascript另類方法實現(xiàn)htmlencode()與htmldecode()函數(shù),結(jié)合實例形式分析了javascript字符編碼與解碼操作的相關(guān)技巧,需要的朋友可以參考下2016-11-11