Skip to content

generatorToAsync

题目描述

实现一个 generatorToAsync 函数,把一个 生成器函数function* () { ... })包装成 返回 Promise 的普通函数,调用后会自动迭代生成器,yield 的值若是 Promise/thenable,会等待其完成再把结果“喂回”生成器,生成器 return 的值成为最终 Promiseresolve 值,且生成器内部 try/catch接住异步错误(靠 iterator.throw(err) 实现)。

要解决什么问题?

  • 将一个 生成器函数function* () { ... })包装成 返回 Promise 的普通函数
    • 调用后会自动迭代生成器;
    • yield 的值若是 Promise/thenable,会等待其完成再把结果“喂回”生成器;
    • 生成器 return 的值成为最终 Promiseresolve 值;
    • 生成器内部 try/catch接住异步错误(靠 iterator.throw(err) 实现)。

类比:这就是早期的 co.wrap 思路;后来 async/await 把它语言化(语义上等价:await ≈ “暂停 + 等 Promise 完成再恢复”,返回值仍是 Promise)。

代码实现

js
export default function generatorToAsync(func) {
  if (typeof func !== 'function' || !('next' in func())) {
    throw new TypeError(`传入的${func}必须是生成器函数`)
  }

  const iterator = func()
  const isPromiseLike = (v) => {
    if (v !== null && (typeof v === 'function' || typeof v === 'object')) {
      return typeof v.then === 'function'
    }
    return false
  }

  return () => new Promise((resolve, reject) => {
    function step(prev) {
      try {
        const { value, done } = iterator.next(prev)
        if (done) {
          resolve(value)
        } else if (isPromiseLike(value)) {
          value.then(step, reject)
        } else {
          step(value)
        }
      } catch (error) { reject(error) }
    }
    step()
  })
}
ts
import type { AsyncFunction, GeneratorFunction } from './types'

export default function generatorToAsync<TReturn = unknown>(
  func: GeneratorFunction<TReturn>,
): AsyncFunction<TReturn> {
  if (typeof func !== 'function') {
    throw new TypeError(`传入的${func}必须是生成器函数`)
  }

  // 检查是否是生成器函数
  const testIterator = func()
  if (!testIterator || typeof testIterator.next !== 'function') {
    throw new TypeError(`传入的${func}必须是生成器函数`)
  }

  const isPromiseLike = (v: unknown): v is PromiseLike<unknown> => {
    if (v !== null && (typeof v === 'function' || typeof v === 'object')) {
      return typeof (v as Record<string, unknown>).then === 'function'
    }

    return false
  }

  return (): Promise<TReturn> =>
    new Promise<TReturn>((resolve, reject) => {
      const iterator = func()

      const step = (prev?: unknown): void => {
        try {
          const result
            = prev === undefined ? iterator.next() : iterator.next(prev)
          const { value, done } = result

          if (done) {
            resolve(value)
          }
          else if (isPromiseLike(value)) {
            value.then(
              (resolvedValue: unknown) => step(resolvedValue),
              (error: unknown) => reject(error),
            )
          }
          else {
            step(value)
          }
        }
        catch (error) {
          reject(error)
        }
      }

      step()
    })
}

工程化模板:健壮版(支持 this/参数/错误注入/thenable 统一)

js
export function generatorToAsync(fn) {
  if (typeof fn !== 'function') {
    throw new TypeError('generatorToAsync: 需要传入生成器函数')
  }

  return function wrapped(...args) {
    const self = this
    let it
    try {
      it = fn.apply(self, args) // 这里才创建迭代器,避免参数检查阶段执行
    } catch (e) {
      return Promise.reject(e)
    }

    if (!it || typeof it.next !== 'function') {
      return Promise.reject(new TypeError('传入的函数未返回迭代器,是否忘了用 function* 声明?'))
    }

    return new Promise((resolve, reject) => {
      const onFulfilled = (res) => {
        let ret
        try { ret = it.next(res) } catch (e) { reject(e); return }
        next(ret)
      }
      const onRejected = (err) => {
        let ret
        try { ret = it.throw ? it.throw(err) : (() => { throw err })() } catch (e) { reject(e); return }
        next(ret)
      }
      function next({ value, done }) {
        if (done) { resolve(value); return }
        // 统一 thenable/普通值
        try { Promise.resolve(value).then(onFulfilled, onRejected) }
        catch (e) { onRejected(e) }
      }
      // 启动
      onFulfilled(undefined)
    })
  }
}

要点回顾

  • this/参数:通过 apply 传入;
  • 错误语义:拒绝时优先 it.throw(err),让生成器有机会处理;
  • thenable 统一Promise.resolve(value) 能同时兼容 Promise、thenable 和普通值;
  • 延迟创建迭代器:直到真正调用包装函数时才创建。

用法示例

a) 基本串行

js
function* task(a, b) {
  const x = yield Promise.resolve(a + b)
  const y = yield new Promise(r => setTimeout(() => r(x * 2), 10))
  return y + 1
}
const run = generatorToAsync(task)
run(2, 3).then(console.log) // 11

b) 生成器内部捕获异步错误

js
function* mayFail() {
  try {
    yield Promise.reject(new Error('boom'))
    return 'ok'
  } catch (e) {
    return 'caught: ' + e.message
  }
}
const run = generatorToAsync(mayFail)
run().then(console.log) // 'caught: boom'

c) 保留 this

js
const o = {
  base: 10,
  *work(n) { return (yield Promise.resolve(n)) + this.base }
}
o.run = generatorToAsync(o.work)
o.run(5).then(console.log) // 15

常见坑

  • ❌ 在参数检查阶段就 func():可能触发副作用/需要参数;应在包装函数里再创建迭代器。
  • ❌ 丢失 this/参数:务必 fn.apply(this, args)
  • ❌ 异步错误不注入生成器:请用 iterator.throw(err),允许 try/catch 处理和清理逻辑运行。
  • ❌ 只判断 value.then:使用 Promise.resolve(value) 统一兼容 thenable/普通值。
  • ❌ 未考虑非生成器:检查 it && typeof it.next === 'function',否则清晰报错。
  • ⚠️ 递归推进:yield 数量极大时递归层数也多;通常问题不大,若担心可用 queueMicrotask(() => ...) 轻量“踢出栈”。

async/await 的关系

  • async 函数 始终返回 Promiseawait 等价于“把 Promise 的结果/异常 注入 到挂起点”再继续执行;
  • 生成器 + 上述包装器 ≈ 手写版 async/await 语义;现代代码建议直接使用 async/await,除非你在学习或做 polyfill/特殊控制流。

可选扩展

  • 并发工具:在生成器里 yield Promise.all([...]) 支持并发;
  • yield 数组/对象自动化:模仿 coyield { a: Promise, b: Promise } 自动转为 Promise.all(需要额外类型分支);
  • 取消/超时:结合 AbortController 或超时包装 Promise;
  • 类型标注:在 TS 中把 fn: (...args) => Generator<Yielded, Returned, Next> 转换为 (...args) => Promise<Returned>

内容基于 MIT 许可 | 保持节奏 · 持续积累