Generator 函数是什么?yield 有什么作用?
简化版
Generator 是可以暂停和恢复执行的函数,用 function* 声明,用 yield 产出值并暂停。调用 Generator 不会立即执行函数体,而是返回迭代器,通过 next() 推进执行。
详细版
示例:
function* gen() {
yield 1;
yield 2;
return 3;
}
const it = gen();
it.next(); // { value: 1, done: false }
it.next(); // { value: 2, done: false }
it.next(); // { value: 3, done: true }
Generator 常用于自定义迭代器、惰性序列、异步流程控制。yield 既能向外产出值,也能接收下一次 next(value) 传回来的值。
完整版教学
一、Generator 是可暂停函数
普通函数一旦调用,会从头执行到结束。Generator 调用后只返回迭代器,函数体还没执行。每次调用 next(),函数执行到下一个 yield 暂停。
这种“暂停点”让函数可以分批产出数据。
二、yield 的两个方向
yield 右边的表达式会作为当前 next() 的返回值:
yield 1;
下一次调用 next(value) 时,传入的 value 会成为上一个 yield 表达式的结果。
这个双向通信机制让 Generator 能做更复杂的流程控制。
三、和迭代协议的关系
Generator 返回的对象实现了迭代器协议,所以可以用于 for...of:
for (const x of gen()) {
console.log(x);
}
注意 for...of 不会拿到 done: true 时的 return 值。
四、实际使用场景
现在异步流程更多用 async/await,但 Generator 仍常用于:
- 自定义可迭代对象。
- 惰性生成大量数据。
- Redux-Saga 等库的副作用控制。
它的价值是把“生成数据”和“消费数据”的节奏分开。
五、面试追问与工程落地
Generator 常见追问是 yield*。它可以把另一个可迭代对象委托给当前 Generator,相当于把子序列展开到当前序列中。递归遍历树、组合多个迭代源时很有用。
还可能追问 Generator 和 async/await 的关系。早期异步流程控制可以用 Generator 加执行器,把 yield 出来的 Promise 等待后再继续 next;async/await 可以看作这类模式的语言级封装,语义更直接。
工程里 Generator 不如 Promise、async/await 高频,但理解它能帮助你看懂 Redux-Saga、惰性遍历、可迭代协议。面试时不要只说“很少用”,要能说明它的暂停恢复能力解决了什么抽象问题。
六、用状态机推演 next、throw 与 return
Generator 对象可理解为 suspendedStart → suspendedYield → completed 的状态机。第一次 next(value) 只是启动函数,传入的 value 没有前一个 yield 可以接收,因此会被忽略;后续 next(value) 才把值送回上一次暂停的 yield 表达式。每次调用返回统一的 { value, done },而 return 语句的值对应最终 done: true 的结果。
function* task() {
try {
const input = yield 10
return input * 2
} finally {
console.log('cleanup')
}
}
const it = task()
it.next(99) // { value: 10, done: false },99 被忽略
it.next(7) // { value: 14, done: true }
| 操作 | 注入生成器的完成类型 | 常见用途 |
|---|---|---|
next(v) | 普通完成 | 继续并把 v 交给上个 yield |
throw(e) | 抛出完成 | 在暂停点抛错,可被内部捕获 |
return(v) | 返回完成 | 提前结束,并执行途经的 finally |
yield* iterable 会委托给另一个迭代器,并转发 next、throw、return 协议,而不只是把若干值做语法展开。若只消费前 2 项就退出 for...of,迭代器的 return 还可获得清理资源的机会。
Generator 的重点不是背诵“可以暂停”,而是能沿着每次恢复时传入的完成类型解释值、异常和清理流程。
七、常见误区与追问
- 误区:调用 Generator 函数会立刻执行函数体。 调用只创建迭代器,首次
next()才开始执行。 - 误区:第一次 next 传入的参数会成为第一个 yield 的结果。 第一次调用前没有暂停中的
yield接收它,所以该参数被忽略。 - 误区:yield 和 return 的结果没有区别。
yield通常返回done: false并可恢复,return完成后返回done: true。 - 追问:Generator 为什么能被 for…of 遍历? 生成器对象实现迭代器协议,其
Symbol.iterator()返回自身并持续提供next()结果。 - 追问:throw 会发生在什么位置? 它把异常注入当前暂停的
yield位置,内部try/catch有机会处理。 - 追问:提前 return 会跳过 finally 吗? 不会,控制流会按正常语义执行途经的
finally,因此可用于清理。 - 追问:yield 的最终表达式值是什么?* 当被委托迭代器结束时,
yield*表达式取得其最终返回值,而不是最后一次产出的普通值。
八、加强记忆
Generator 可以记成“带暂停键的函数”。yield 是暂停点,也是产出点;next() 是继续按钮。它天然实现迭代器,适合惰性序列和流程控制。
再配合 next、throw、return 三种恢复方式推演一次,Generator 的控制流就会从口诀变成可验证的状态机。