雖然如今使用 async 函數 就能夠替代 Generator 執行器了,不過了解下 Generator 執行器的原理仍是挺有必要的。es6
若是你不瞭解 Generator,那麼你須要看這裏。json
例子均可以在 Console 中運行的(谷歌版本 76.0.3809.100),都是以最新瀏覽器支持的 JavaScript 特性來編寫的,不考慮兼容性。數組
Generator 是 Generator function 運行後返回的對象。promise
有 Generator next 函數的特性,next 函數運行後會返回以下結構:瀏覽器
{ value: 'world', done: false }
複製代碼
或者bash
{ value: undefined, done: true }
複製代碼
那麼咱們可使用遞歸運行 next 函數,若是 done 爲 true 則中止 next 函數的運行。app
yield 表達式自己沒有返回值,或者說老是返回 undefined。async
next 方法能夠帶一個參數,該參數就會被看成上一個 yield 表達式的返回值。函數
因爲 next 方法的參數表示上一個 yield 表達式的返回值,因此在第一次使用 next 方法時,傳遞參數是無效的。ui
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)
// })
複製代碼