Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
228 lines (192 sloc) 4.4 KB

Generator 使用规律

从一道题目开始:

function* gen() {
  const a = yield 1
  console.log(a)
}

为了让其能成功打印出 1, 设计如下函数:

function step(gen) {
  const it = gen()
  let result
  return function() {
    result = it.next(result).value
  }
}

进行如下调用:

var a = step(gen)
a()
a() // 1

从这个题目总结出规律:

  • next 的调用数比 yield 的调用数多 1;
  • 第一个 next 传参无效, 从第二个 next 开始传参有效并会作为 yield 的结果返回;

生成器中的 yield/next 除了控制能力外还有双向的消息通知能力:

  • yield 后面跟的值能通过 it.next().value 取到
  • it.next() 括号中的值又能作为 yield 的结果返回

yield 暂停的位置

function* foo(url) {
  try {
    const value = yield request(url)
    console.log(value)
  } catch (err) {
    ...
  }
}

const it = foo('http://some.url.1')

yield 后面跟着的语句执行完再进入暂停状态的, 在如上代码中, 当执行 it.next() 时, 可以稍加转换为如下形式:

function* foo(url) {
  try {
    const promise = request(url) // 当执行 it.next() 时, 这里是被执行的
    const value = yield promise  // 这里被暂停
    console.log(value)
  } catch (err) {
    ...
  }
}

遇到 return/throw

  • 遇到 return
function* gen() {
  yield 1
  return 2
  console.log('是否执行')
}

const it = gen()
it.next() // {value: 1, done: false}
it.next() // {value: 2, done: true}
it.next() // {value: undefined, done: true}

总结: 遇到 return, generator 函数结束中断, done 变为 true;

  • 遇到 iteratorthrow
function* gen() {
  yield 1
  console.log('是否执行')
}

var it = gen()
it.throw(new Error('boom')) // Error: boom
it.next()                   // {value: undefined, done: true}

总结: 遇到 iteratorthrow, generator 函数运行中断, done 变为 true;

Generator 的简单实现

Generator 是一个返回迭代器的函数, 下面是其简版实现:

function foo(url) {
  var state
  var val
  function process(v) {
    switch (state) {
      case 1:
        console.log('requesting:', url)
        return request(url)
      case 2:
        val = v
        console.log(val)
        return
      case 3:
        var err = val
        console.log('Oops:', err)
        return false
    }
  }
  return {
    next: function(v) {
      if (!state) {
        state = 1
        return {
          done: false,
          value: process()
        }
      } else if (state === 1) {
        state = 2
        return {
          done: true,
          value: process(v)
        }
      } else {
        return {
          done: true,
          value: undefined
        }
      }
    },
    throw: function() {
      if (state === 1) {
        state = 3
        return {
          done: true,
          value: process(e)
        }
      } else {
        throw e
      }
    }
  }
}

var it = foo('http://some.url.1')

Generator 函数的异步应用

co 库来说, 现在已经统一为 Generator + Promise 的调用方式, 下面进行简单的模拟:

co(function* () {
  const result = yield Promise.resolve(true)
  console.log(result) // true
})
// 简版 promise
function co(gen) {
  const it = gen()
  const step = function(data) {
    const result = it.next(data)
    if (result.done) {
      return result.value
    }
    result.value.then((data) => {
      step(data)
    })
  }
  step()
}

观察 co 库发现, co 函数后返回的是 promise, 使用如下:

co(function* () {
  const result = yield Promise.resolve(true)
  return result // 这里有个语法, it.next() 碰到 return 后, 其值会变为 { value: result, done: true } 的形式
}).then((data) => {
  console.log(data) // true
})

我们再对其稍加改造, 使之更加添近 co 库:

function co(gen) {
  return new Promise((resolve, reject) => {
    const it = gen()
    let result
    const step = function(fn) {
      try {
        result = fn()
      } catch(e) {
        return reject(e)
      }
      if (result.done) { return resolve(result.value) }
      result.value.then((data) => {
        step(() => it.next(data))
      }, (err) => {
        step(() => it.throw(err)) // 这里为了让抛错直接在 generator 消化, 所以 step 内改传函数
      })
    }
    step(() => it.next())
  })
}
You can’t perform that action at this time.