generator详解
基本概念
Generator(生成器)是一个可以 暂停 和 恢复执行 的函数。
function* gen() {
yield 1;
yield 2;
return 3
}
// 调用
const g = gen()
g.next(); // { value:1, done:false }
g.next(); // { value:2, done:false }
g.next(); // { value:3, done:true }
g.next(); // { value:undefined, done:true }
生成器的定义与传统的函数非常相似,不同的地方在于生成器的关键字是function*,带了星号*,并且使用了关键字yield来指定生成器的执行步骤。
yield可以看成是一个暂停器,生成器执行到 yield 的时候,就会停止执行并且将yield后面的值作为返回值,并记录下当前运行的上下文,等待下一次调用next()方法时恢复上下文和yield后面的值,并继续执行,直到生成器执行结束。
next()的机制能够让生成器恢复执行,每一次调用,都会让状态机推进一步。
next还能向生成器内部传值。
function* gen() {
const x = yield '请输入 x';
const y = yield '请输入 y';
return x + y;
}
const g = gen();
console.log(g.next(111111)); // pause: yield '请输入 x'
console.log(g.next(10)); // 10 赋给 x
console.log(g.next(20)); // 20 赋给 y → return 30
以上代码中,可以看出,在第一次调用next时,next传参是没用的,只有从第二次开始传参才有用。需要注意,先进行yield,再赋值。
通常情况下,我们可以使用生成器来处理状态化的问题,在文件读写、网络请求等场景中都可以使用。
使用场景
- 代替回调的异步流程控制,如今基本使用async/await 的方式。
function* task() {
const a = yield ajax('/api/a');
const b = yield ajax('/api/b?a=' + a);
return b;
}
- 实现可中断的长任务(节流 / 分帧),可以结合
requestIdleCallback()做分片处理
function* bigTask() {
for (let i=0;i<100000;i++){
doSomething(i);
if (i % 100 === 0) yield; // 让出主线程
}
}
- 创建数据序列(迭代器)
function* count() {
let i = 0;
while (true) yield i++;
}
- 实现状态机
function* fsm() {
while (true) {
yield 'state1';
yield 'state2';
yield 'state3';
}
}
- yield:组合多个 generator*
function* a() { yield 1; yield 2; }
function* b() { yield 3; yield 4; }
function* all() {
yield* a();
yield* b();
}
console.log([...all()]) // [1,2,3,4]
Generator VS async/await 的关系
async/await 是对 generator 的终极封装。
async/await 的内部原理核心就是:
Generator + 自动执行器 + Promise = async function
Generator 本身就能写 async/await 的效果:
function* gen() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(a + 1);
return b;
}
async/await 多了一个“自动执行器”:
function run(g) {
const it = g();
function next(v) {
const {value, done} = it.next(v);
if (done) return value;
value.then(res => next(res));
}
next();
}
async/await的终极实现:
function run(genFn) {
const gen = genFn(); // 拿到 generator 实例
return new Promise((resolve, reject) => {
function step(nextF, arg) {
let next;
try {
next = nextF.call(gen, arg); // 调用 next() 或 throw()
} catch (err) {
return reject(err); // generator 内部报错
}
// 结束了
if (next.done) {
return resolve(next.value);
}
// value 必须是 Promise
Promise.resolve(next.value)
.then(v => step(gen.next, v)) // 成功 → 注入 generator
.catch(err => step(gen.throw, err)); // 失败 → throw 进入 generator
}
step(gen.next); // 启动 generator
});
}