background

如何写一个让面试官满意的 Generator 执行器?

虽然现在使用 async 函数 就可以替代 Generator 执行器了,不过了解下 Generator 执行器的原理还是挺有必要的。如果你不了解 Generator,那么你需要看这里。

{"first":"Yang","last":"Liu"}

Yang

MR

虽然现在使用 async 函数 就可以替代 Generator 执行器了,不过了解下 Generator 执行器的原理还是挺有必要的。

如果你不了解 Generator,那么你需要看这里

例子都可以在 Console 中运行的(谷歌版本 76.0.3809.100),都是以最新浏览器支持的 JavaScript 特性来编写的,不考虑兼容性。

术语区分

Generator 是 Generator function 运行后返回的对象。

执行器的原理

原理一

有 Generator next 函数的特性,next 函数运行后会返回如下结构:

{ value: 'world', done: false }

或者

{ value: undefined, done: true }

那么我们可以使用递归运行 next 函数,如果 done 为 true 则停止 next 函数的运行。

原理二

yield 表达式本身没有返回值,或者说总是返回 undefined。

next 方法可以带一个参数,该参数就会被当作上一个 yield 表达式的返回值。

由于 next 方法的参数表示上一个 yield 表达式的返回值,所以在第一次使用 next 方法时,传递参数是无效的。

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)

简单的执行器

简单的执行器代码如下:

/**
 * 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()
}

代码并不复杂,最简单的执行器就出来了。如果你是一步一步的看文章过来的,都理解了原理,那么这些代码也很好理解。

考虑 yield 的类型是 Promise

上面的代码执行如下的 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 {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 Promise.resolve(value).then(finalValue => {
        return recursion(finalValue)
      })
    }
  }
  return recursion()
}

这样就再运行 generatorExecuter(test) 就没问题了。

考虑 yield 的类型是 Generator 函数

上面的代码执行如下的 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 函数
 */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false
  }
}

/**
 * 运行 generator
 * @param {GeneratorFunction} generator generator函数运行
 */
function generatorExecuter(generatorFunc) {
  const generator = generatorFunc()
  function recursionGeneratorNext(prevValue) {
    const next = generator.next(prevValue)
    let value = next.value
    const done = next.done

    if (isGerneratorFunction(value)) {
      value = generatorExecuter(value)
    }

    if (done) {
      return Promise.resolve(value)
    } else {
      // value 可能是 Promise 对象
      return Promise.resolve(value).then(finalValue => {
        return recursionGeneratorNext(finalValue)
      })
    }
  }
  return recursionGeneratorNext()
}

这样就再运行 generatorExecuter(test) 就没问题了。

考虑 yield 的类型是 Generator

上面的代码执行如下的 Generator 函数是不正确的:

function* aFun(value) {
  return value
}

function* test() {
  const a = yield aFun('a')
  console.log(a)
  return true
}
generatorExecuter(test)

运行上面的代码后,test 函数 console.log 输出的不是 a,而是 aFun {<suspended>}

那么我们代码需要这样处理:

/**
 * 是否是 generator
 */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false
  }
}

/**
 * 是否是 generator 函数
 */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false
  }
}

/**
 * 运行 generator
 * @param {Generator} generator generator函数运行后返回的对象
 */
function runGernerator(generator) {
  function recursionGeneratorNext(prevValue) {
    const next = generator.next(prevValue)
    let value = next.value
    const done = next.done

    if (isGernerator(value)) {
      value = runGernerator(value)
    } else if (isGerneratorFunction(value)) {
      value = generatorExecuter(value)
    }

    if (done) {
      return Promise.resolve(value)
    } else {
      // value 可能是 Promise 对象
      return Promise.resolve(value).then(finalValue => {
        return recursionGeneratorNext(finalValue)
      })
    }
  }
  return recursionGeneratorNext()
}

/**
 * generator 执行器
 * @param {Generator || GeneratorFunction} generatorFunc generator 函数或者 generator
 * @return {Promise} 返回一个 Promise 对象
 */
function generatorExecuter(generatorFunc) {
  let generator
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  } else if (isGernerator(generatorFunc)) {
    generator = generatorFunc
  } else {
    throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFuntion or Generator.'
    )
  }
  return runGernerator(generator)
}

这样就再运行 generatorExecuter(test) 就没问题了。

最终版

Generator 执行器没想的那么难,花点时间就可以吃透了。

/**
 * 是否是 generator
 */
function isGernerator(target) {
  if (Object.prototype.toString.apply(target) === '[object Generator]') {
    return true
  } else {
    return false
  }
}

/**
 * 是否是 generator 函数
 */
function isGerneratorFunction(target) {
  if (
    Object.prototype.toString.apply(target) === '[object GeneratorFunction]'
  ) {
    return true
  } else {
    return false
  }
}

/**
 * 运行 generator
 * @param {Generator} generator generator函数运行后返回的对象
 */
function runGernerator(generator) {
  function recursionGeneratorNext(prevValue) {
    const next = generator.next(prevValue)
    let value = next.value
    const done = next.done

    if (isGernerator(value)) {
      value = runGernerator(value)
    } else if (isGerneratorFunction(value)) {
      value = generatorExecuter(value)
    }

    if (done) {
      return Promise.resolve(value)
    } else {
      // value 可能是 Promise 对象
      return Promise.resolve(value).then(finalValue => {
        return recursionGeneratorNext(finalValue)
      })
    }
  }
  return recursionGeneratorNext()
}

/**
 * generator 执行器
 * @param {Generator || GeneratorFunction} generatorFunc generator 函数或者 generator
 * @return {Promise} 返回一个 Promise 对象
 */
function generatorExecuter(generatorFunc) {
  let generator
  if (isGerneratorFunction(generatorFunc)) {
    generator = generatorFunc()
  } else if (isGernerator(generatorFunc)) {
    generator = generatorFunc
  } else {
    throw new TypeError(
      'Expected the generatorFunc to be a GeneratorFuntion or Generator.'
    )
  }
  return runGernerator(generator)
}

运行例子如下,直接在谷歌 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* 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)
  return 'over'
}

generatorExecuter(all).then(value => {
  console.log(value)
})

// 或者
// generatorExecuter(all()).then(value => {
//   console.log(value)
// })
 
 
本文章转载自SegmentFault。

即将开的培训课程

DevOps实战班

DevOps实战班

城市: 悉尼
课程安排:面授+远程
开课时间:Nov. 23
线下早鸟价:3960
线上早鸟价:3190
早鸟截止日期:Nov. 02
课程更新
Atlassian SRE老师
掌握DevOps全家桶
系统化学习全方位DevOps
通过完整项目贯穿课程
数据全栈(DA+DE+DS)项目班 第3期

数据全栈(DA+DE+DS)项目班 第3期

城市: 悉尼
课程安排:每周两次,每次三小时
开课时间:Dec. 01
原价:5200
线下原价:5200
线下早鸟价:4700
线上原价:3800
线上早鸟价:3500
早鸟截止日期:Nov. 01
课程更新
对接就业
数据分析
数据科学
数据工程
实习机会
工作内推
Web入门班·Wordpress

Web入门班·Wordpress

城市: 布里斯班
课程安排:一周2课,每次3小时
开课时间:Dec. 01
原价:3200
线下早鸟价:3000
线上原价:2500
线上早鸟价:2300
早鸟截止日期:Nov. 20
课程更新
Wordpress
Php
jQuery
HTML
Javascript
MySql
cpanal
Web全栈班第9期

Web全栈班第9期

城市: 布里斯班
课程安排:面授+远程
开课时间:Dec. 01
原价:5500
线下早鸟价:5000
线上早鸟价:4600
早鸟截止日期:Nov. 22
课程更新
产品经理加入
先培训后实习
Google大神算法
Web全栈班第9期

Web全栈班第9期

城市: 墨尔本
课程安排:面授+远程
开课时间:Dec. 01
原价:5500
线下早鸟价:5000
线上早鸟价:4600
早鸟截止日期:Nov. 22
课程更新
全新老师阵容
新增Java后端
先培训后实习
Web全栈班第9期

Web全栈班第9期

城市: 悉尼
课程安排:面授+远程
开课时间:Dec. 01
原价:5500
线下早鸟价:5100
线上原价:4600
早鸟截止日期:Nov. 22
课程更新
大神级导师
与企业密切结合
面向企业要求培训