generatorToAsync
题目描述
实现一个 generatorToAsync 函数,把一个 生成器函数(function* () { ... })包装成 返回 Promise 的普通函数,调用后会自动迭代生成器,yield 的值若是 Promise/thenable,会等待其完成再把结果“喂回”生成器,生成器 return 的值成为最终 Promise 的 resolve 值,且生成器内部 try/catch 能接住异步错误(靠 iterator.throw(err) 实现)。
要解决什么问题?
- 将一个 生成器函数(
function* () { ... })包装成 返回 Promise 的普通函数:- 调用后会自动迭代生成器;
yield的值若是 Promise/thenable,会等待其完成再把结果“喂回”生成器;- 生成器
return的值成为最终Promise的 resolve 值; - 生成器内部
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) // 11b) 生成器内部捕获异步错误
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函数 始终返回 Promise;await等价于“把 Promise 的结果/异常 注入 到挂起点”再继续执行;- 生成器 + 上述包装器 ≈ 手写版
async/await语义;现代代码建议直接使用async/await,除非你在学习或做 polyfill/特殊控制流。
可选扩展
- 并发工具:在生成器里
yield Promise.all([...])支持并发; - yield 数组/对象自动化:模仿
co把yield { a: Promise, b: Promise }自动转为Promise.all(需要额外类型分支); - 取消/超时:结合
AbortController或超时包装 Promise; - 类型标注:在 TS 中把
fn: (...args) => Generator<Yielded, Returned, Next>转换为(...args) => Promise<Returned>。