雖然如今使用 async 函數 就能夠替代 Generator 執行器了,不過了解下 Generator 執行器的原理仍是挺有必要的。es6
若是你不瞭解 Generator,那麼你須要看這裏。json
例子均可以在 Console 中運行的(谷歌版本 76.0.3809.100),都是以最新瀏覽器支持的 JavaScript 特性來編寫的,不考慮兼容性。數組
Generator 是 Generator function 運行後返回的對象。promise
有 Generator next 函數的特性,next 函數運行後會返回以下結構:瀏覽器
{ value: 'world', done: false }
或者app
{ value: undefined, done: true }
那麼咱們可使用遞歸運行 next 函數,若是 done 爲 true 則中止 next 函數的運行。async
yield 表達式自己沒有返回值,或者說老是返回 undefined。函數
next 方法能夠帶一個參數,該參數就會被看成上一個 yield 表達式的返回值。prototype
因爲 next 方法的參數表示上一個 yield 表達式的返回值,因此在第一次使用 next 方法時,傳遞參數是無效的。code
function* test() { const a = yield "a" console.log(a) return true } const gen = test() // 第一次 next() 會卡在 yield a 中,next 返回 {value: "a", done: false} // 第一個 next() 函數的參數是無效的,無需傳遞 const nextOne = gen.next() // 傳遞參數 nextOne.value // test 函數中的 console.log 輸出 'a' // 第二次的 next() 返回值爲 {value: true, done: true} gen.next(nextOne.value)
gen.throw(exception)
能夠拋出異常,並恢復生成器的執行(前提須要 try catch yield 語句),返回帶有 done 及 value 兩個屬性的對象。並且該異常能夠經過 try...catch
塊進行捕獲。那麼咱們能夠經過 gen.throw 拋出 Promise 錯誤,這樣就可使用 try catch
攔截 Promise 的錯誤了。
function* gen() { let a try { a = yield 42 } catch (e) { console.log('Error caught!') } console.log(a) yield 33 } var g = gen() g.next() // { value: 42, done: false } g.throw(new Error('Something went wrong')) // "Error caught!"
簡單的執行器代碼以下:
/** * generator 執行器 * @param {Function} func generator 函數 * @return {Promise} 返回一個 Promise 對象 */ function generatorExecuter(func) { const gen = func() function recursion(prevValue) { const next = gen.next(prevValue) const value = next.value const done = next.done if (done) { return Promise.resolve(value) } else { return recursion(value) } } return recursion() }
代碼並不複雜,最簡單的執行器就出來了。若是你是一步一步的看文章過來的,都理解了原理,那麼這些代碼也很好理解。
上面的代碼執行以下的 Generator 函數是不正確的:
function* test() { const a = yield Promise.resolve('a') console.log(a) return true } generatorExecuter(test)
運行上面的代碼後,test 函數 console.log 輸出的不是 a
,而是 Promise {<resolved>: "a"}
。
那麼咱們代碼須要這樣處理:
/** * generator 執行器 * @param {GeneratorFunction} generatorFunc generator 函數 * @return {Promise} 返回一個 Promise 對象 */ function generatorExecuter(generatorFunc) { return new Promise((resolve) => { const generator = generatorFunc() // 觸發第一次執行下面定義的 next 函數 onFullfilled() /** * Promise 成功處理的回調函數 * 回調函數會執行 generator.next() * @param {Any} value yield 表達式的返回值 */ function onFullfilled(value) { let result // next() 會一步一步的執行 generator 函數體的代碼, result = generator.next(value) next(result) } function next(result) { const value = result.value const done = result.done if (done) { return resolve(value) } else { return Promise.resolve(value).then(onFullfilled) } } }) }
這樣就再運行 generatorExecuter(test) 就沒問題了。
這裏用到了上面提到的原理三,若是不清楚能夠回去看看。
以下代碼,使用上面的執行器是沒法 try catch 到 yield 錯誤的:
function* test() { try { const a = yield Promise.reject('error') }catch (err) { console.log('發生錯誤了:', err) } return true } generatorExecuter(test)
運行上面代碼後,會報錯 Uncaught (in promise) error
,而不是攔截後輸出 發生錯誤了: error
。
要改爲這樣才行(使用 gen.throw ):
/** * generator 執行器 * @param {GeneratorFunction} generatorFunc generator 函數 * @return {Promise} 返回一個 Promise 對象 */ function generatorExecuter(generatorFunc) { return new Promise((resolve, reject) => { const generator = generatorFunc() // 觸發第一次執行下面定義的 next 函數 onFullfilled() /** * Promise 成功處理的回調函數 * 回調函數會執行 generator.next() * @param {Any} value yield 表達式的返回值 */ function onFullfilled(value) { let result // next() 會一步一步的執行 generator 函數體的代碼, // 若是報錯,咱們須要攔截,而後 reject try { // yield 表達式自己沒有返回值,或者說老是返回 undefined。 // generator.next 方法能夠帶一個參數,該參數就會被看成上一個 yield 表達式的返回值。 // 因爲 generator.next 方法的參數表示上一個 yield 表達式的返回值, // 因此在第一次使用 generator.next 方法時,傳遞參數是無效的。 result = generator.next(value) } catch (error) { return reject(error) } next(result) } /** * Promise 失敗處理的回調函數 * 回調函數會執行 generator.throw() ,這樣能夠 try catch 攔截 yield xxx 的錯誤 * @param {Any} reason 失敗緣由 */ function onRejected(reason) { let result try { // 這裏 try catch 是捕獲恢復生成器執行的時候,代碼發生錯誤的狀況 // gen.throw() 方法用來向生成器拋出異常,並恢復生成器的執行, // 返回帶有 done 及 value 兩個屬性的對象。 result = generator.throw(reason) } catch (error) { return reject(error) } // gen.throw() 的錯誤被捕獲後,能夠繼續執行下去,不然終止後續的運行 // 這能夠達到同步效果 //(不是上面的 try catch,是使用者 try catch yield 語句) next(result) } function next(result) { const value = result.value const done = result.done if (done) { return resolve(value) } else { return Promise.resolve(value).then(onFullfilled, onRejected) } } }) }
考慮 yield 的其餘類型,如 generator 函數,能夠把這些類型適配爲 Promise 就很好處理了。
上面的代碼執行以下的 Generator 函數是不正確的:
function* aFun() { return 'a' } function* test() { const a = yield aFun console.log(a) return true } generatorExecuter(test)
運行上面的代碼後,test 函數 console.log 輸出的不是 a
,而是輸出下面的字符串:
ƒ* aFun() { return 'a' }
那麼咱們代碼須要這樣處理:
/** * generator 執行器 * @param {GeneratorFunction | Generator} generatorFunc Generator 函數或者 Generator * @return {Promise} 返回一個 Promise 對象 */ function generatorExecuter(generatorFunc) { if (!isGernerator(generatorFunc) && !isGerneratorFunction(generatorFunc)) { throw new TypeError( 'Expected the generatorFunc to be a GeneratorFunction or a Generator.' ) } let generator = generatorFunc if (isGerneratorFunction(generatorFunc)) { generator = generatorFunc() } return new Promise((resolve, reject) => { // 觸發第一次執行下面定義的 next 函數 onFullfilled() /** * Promise 成功處理的回調函數 * 回調函數會執行 generator.next() * @param {Any} value yield 表達式的返回值 */ function onFullfilled(value) { let result // next() 會一步一步的執行 generator 函數體的代碼, // 若是報錯,咱們須要攔截,而後 reject try { // yield 表達式自己沒有返回值,或者說老是返回 undefined。 // generator.next 方法能夠帶一個參數,該參數就會被看成上一個 yield 表達式的返回值。 // 因爲 generator.next 方法的參數表示上一個 yield 表達式的返回值, // 因此在第一次使用 generator.next 方法時,傳遞參數是無效的。 result = generator.next(value) } catch (error) { return reject(error) } next(result) } /** * Promise 失敗處理的回調函數 * 回調函數會執行 generator.throw() ,這樣能夠 try catch 攔截 yield xxx 的錯誤 * @param {Any} reason 失敗緣由 */ function onRejected(reason) { let result try { // 這裏 try catch 是捕獲恢復生成器執行的時候,代碼發生錯誤的狀況 // gen.throw() 方法用來向生成器拋出異常,並恢復生成器的執行, // 返回帶有 done 及 value 兩個屬性的對象。 result = generator.throw(reason) } catch (error) { return reject(error) } // gen.throw() 的錯誤被捕獲後,能夠繼續執行下去,不然終止後續的運行 // 這能夠達到同步效果 //(不是上面的 try catch,是使用者 try catch yield 語句) next(result) } function next(result) { const value = toPromise(result.value) const done = result.done if (done) { return resolve(value) } else { return value.then(onFullfilled, onRejected) } } }) } /** * 考慮 yield 的其餘類型,如 generator 函數 * 能夠把這些類型適配爲 Promise 就很好處理了 */ function toPromise(value) { if (isGerneratorFunction(value) || isGernerator(value)) { // generatorExecuter 返回 Promise return generatorExecuter(value) } else { // 字符串、對象、數組、Promise 等都轉成 Promise return Promise.resolve(value) } } /** * 是不是 generator 函數 */ function isGerneratorFunction(target) { if ( Object.prototype.toString.apply(target) === '[object GeneratorFunction]' ) { return true } else { return false } } /** * 是不是 generator */ function isGernerator(target) { if (Object.prototype.toString.apply(target) === '[object Generator]') { return true } else { return false } }
這樣就再運行 generatorExecuter(test) 就沒問題了。
Generator 執行器沒想的那麼難,花點時間就能夠吃透了。
/** * generator 執行器 * @param {GeneratorFunction | Generator} generatorFunc Generator 函數或者 Generator * @return {Promise} 返回一個 Promise 對象 */ function generatorExecuter(generatorFunc) { if (!isGernerator(generatorFunc) && !isGerneratorFunction(generatorFunc)) { throw new TypeError( 'Expected the generatorFunc to be a GeneratorFunction or a Generator.' ) } let generator = generatorFunc if (isGerneratorFunction(generatorFunc)) { generator = generatorFunc() } return new Promise((resolve, reject) => { // 觸發第一次執行下面定義的 next 函數 onFullfilled() /** * Promise 成功處理的回調函數 * 回調函數會執行 generator.next() * @param {Any} value yield 表達式的返回值 */ function onFullfilled(value) { let result // next() 會一步一步的執行 generator 函數體的代碼, // 若是報錯,咱們須要攔截,而後 reject try { // yield 表達式自己沒有返回值,或者說老是返回 undefined。 // generator.next 方法能夠帶一個參數,該參數就會被看成上一個 yield 表達式的返回值。 // 因爲 generator.next 方法的參數表示上一個 yield 表達式的返回值, // 因此在第一次使用 generator.next 方法時,傳遞參數是無效的。 result = generator.next(value) } catch (error) { return reject(error) } next(result) } /** * Promise 失敗處理的回調函數 * 回調函數會執行 generator.throw() ,這樣能夠 try catch 攔截 yield xxx 的錯誤 * @param {Any} reason 失敗緣由 */ function onRejected(reason) { let result try { // 這裏 try catch 是捕獲恢復生成器執行的時候,代碼發生錯誤的狀況 // gen.throw() 方法用來向生成器拋出異常,並恢復生成器的執行, // 返回帶有 done 及 value 兩個屬性的對象。 result = generator.throw(reason) } catch (error) { return reject(error) } // gen.throw() 的錯誤被捕獲後,能夠繼續執行下去,不然終止後續的運行 // 這能夠達到同步效果 //(不是上面的 try catch,是使用者 try catch yield 語句) next(result) } function next(result) { const value = toPromise(result.value) const done = result.done if (done) { return resolve(value) } else { return value.then(onFullfilled, onRejected) } } }) } /** * 考慮 yield 的其餘類型,如 generator 函數 * 能夠把這些類型適配爲 Promise 就很好處理了 */ function toPromise(value) { if (isGerneratorFunction(value) || isGernerator(value)) { // generatorExecuter 返回 Promise return generatorExecuter(value) } else { // 字符串、對象、數組、Promise 等都轉成 Promise return Promise.resolve(value) } } /** * 是不是 generator 函數 */ function isGerneratorFunction(target) { if ( Object.prototype.toString.apply(target) === '[object GeneratorFunction]' ) { return true } else { return false } } /** * 是不是 generator */ function isGernerator(target) { if (Object.prototype.toString.apply(target) === '[object Generator]') { return true } else { return false } }
運行例子以下,直接在谷歌 console 運行便可:
// 例子 function* one() { return 'one' } function* two() { return yield 'two' } function* three() { return Promise.resolve('three') } function* four() { return yield Promise.resolve('four') } function* five() { const a = yield new Promise(resolve => { setTimeout(() => { resolve('waiting five over') }, 1000) }) console.log(a) return 'five' } function* err() { const a = 2 a = 3 return a } function* all() { const a = yield one() console.log(a) const b = yield two() console.log(b) const c = yield three() console.log(c) const d = yield four() console.log(d) const e = yield five() console.log(e) try { yield err() } catch (err) { console.log('發生了錯誤', err) } return 'over' } generatorExecuter(all).then(value => { console.log(value) }) // 或者 // generatorExecuter(all()).then(value => { // console.log(value) // })