Generator 使用规律

从一道题目开始:

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

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

  1. function step(gen) {
  2. const it = gen()
  3. let result
  4. return function() {
  5. result = it.next(result).value
  6. }
  7. }

进行如下调用:

  1. var a = step(gen)
  2. a()
  3. a() // 1

从这个题目总结出规律:

  • next 的调用数比 yield 的调用数多 1;
  • 第一个 next 传参无效, 从第二个 next 开始传参有效并会作为 yield 的结果返回;
    生成器中的 yield/next 除了控制能力外还有双向的消息通知能力:
  • yield 后面跟的值能通过 it.next().value 取到
  • it.next() 括号中的值又能作为 yield 的结果返回

    yield 暂停的位置

    1. function* foo(url) {
    2. try {
    3. const value = yield request(url)
    4. console.log(value)
    5. } catch (err) {
    6. ...
    7. }
    8. }
    9. const it = foo('http://some.url.1')
    yield 后面跟着的语句执行完再进入暂停状态的, 在如上代码中, 当执行 it.next() 时, 可以稍加转换为如下形式:
    1. function* foo(url) {
    2. try {
    3. const promise = request(url) // 当执行 it.next() 时, 这里是被执行的
    4. const value = yield promise // 这里被暂停
    5. console.log(value)
    6. } catch (err) {
    7. ...
    8. }
    9. }

    遇到 return/throw

  • 遇到 return
    1. function* gen() {
    2. yield 1
    3. return 2
    4. console.log('是否执行')
    5. }
    6. const it = gen()
    7. it.next() // {value: 1, done: false}
    8. it.next() // {value: 2, done: true}
    9. it.next() // {value: undefined, done: true}
    总结: 遇到 return, generator 函数结束中断, done 变为 true;
  • 遇到 iteratorthrow
    1. function* gen() {
    2. yield 1
    3. console.log('是否执行')
    4. }
    5. var it = gen()
    6. it.throw(new Error('boom')) // Error: boom
    7. it.next() // {value: undefined, done: true}
    总结: 遇到 iteratorthrow, generator 函数运行中断, done 变为 true;

    Generator 的简单实现

    Generator 是一个返回迭代器的函数, 下面是其简版实现:
    1. function foo(url) {
    2. var state
    3. var val
    4. function process(v) {
    5. switch (state) {
    6. case 1:
    7. console.log('requesting:', url)
    8. return request(url)
    9. case 2:
    10. val = v
    11. console.log(val)
    12. return
    13. case 3:
    14. var err = val
    15. console.log('Oops:', err)
    16. return false
    17. }
    18. }
    19. return {
    20. next: function(v) {
    21. if (!state) {
    22. state = 1
    23. return {
    24. done: false,
    25. value: process()
    26. }
    27. } else if (state === 1) {
    28. state = 2
    29. return {
    30. done: true,
    31. value: process(v)
    32. }
    33. } else {
    34. return {
    35. done: true,
    36. value: undefined
    37. }
    38. }
    39. },
    40. throw: function() {
    41. if (state === 1) {
    42. state = 3
    43. return {
    44. done: true,
    45. value: process(e)
    46. }
    47. } else {
    48. throw e
    49. }
    50. }
    51. }}
    52. var it = foo('http://some.url.1')

    Generator 函数的异步应用

    co 库来说, 现在已经统一为 Generator + Promise 的调用方式, 下面进行简单的模拟:
    1. co(function* () {
    2. const result = yield Promise.resolve(true)
    3. console.log(result) // true
    4. })
    1. // 简版 promise
    2. function co(gen) {
    3. const it = gen()
    4. const step = function(data) {
    5. const result = it.next(data)
    6. if (result.done) {
    7. return result.value
    8. }
    9. result.value.then((data) => {
    10. step(data)
    11. })
    12. }
    13. step()
    14. }
    观察 co 库发现, co 函数后返回的是 promise, 使用如下:
    1. co(function* () {
    2. const result = yield Promise.resolve(true)
    3. return result // 这里有个语法, it.next() 碰到 return 后, 其值会变为 { value: result, done: true } 的形式
    4. }).then((data) => {
    5. console.log(data) // true
    6. })
    我们再对其稍加改造, 使之更加添近 co 库:
  1. function co(gen) {
  2. return new Promise((resolve, reject) => {
  3. const it = gen()
  4. let result
  5. const step = function(fn) {
  6. try {
  7. result = fn()
  8. } catch(e) {
  9. return reject(e)
  10. }
  11. if (result.done) { return resolve(result.value) }
  12. result.value.then((data) => {
  13. step(() => it.next(data))
  14. }, (err) => {
  15. step(() => it.throw(err)) // 这里为了让抛错直接在 generator 消化, 所以 step 内改传函数
  16. })
  17. }
  18. step(() => it.next())
  19. })
  20. }