虽然如今使用 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) // })