Generator 迭代器协议 & co 库底层原理+实战
这两个是理解 Generator 的「关键底层知识点」,也是面试中容易被追问的细节——迭代器协议是 Generator 能被遍历的基础,co 库是 Generator 实现异步流程控制的核心,我用最通俗的语言+代码拆解清楚。
一、迭代器协议(Iterator Protocol):Generator 能“暂停/遍历”的底层规则
1. 先搞懂:什么是“协议”?
协议就是「约定好的规则」——ES6 规定了两套和遍历相关的协议:
-
可迭代协议(Iterable Protocol) :一个对象只要有
[Symbol.iterator]()方法,且该方法返回一个「迭代器对象」,就称这个对象“可迭代”(比如 Array、Set、Map、Generator 对象都符合); -
迭代器协议(Iterator Protocol) :一个对象只要有
next()方法,且next()返回{ value: 产出值, done: 是否完成 }格式的对象,就称这个对象是“迭代器”。
2. Generator 与迭代器协议的关系
Generator 函数调用后返回的「生成器对象」,同时满足可迭代协议 + 迭代器协议——这是它能被 for...of 遍历、能暂停/恢复的核心原因。
验证:生成器对象的协议合规性
function* gen() {
yield 1;
yield 2;
}
const g = gen(); // 生成器对象
// 1. 验证迭代器协议:有next(),返回{value, done}
console.log(g.next()); // { value: 1, done: false }
console.log(g.next()); // { value: 2, done: false }
console.log(g.next()); // { value: undefined, done: true }
// 2. 验证可迭代协议:有[Symbol.iterator](),且返回自身(迭代器)
console.log(typeof g[Symbol.iterator] === 'function'); // true
console.log(g[Symbol.iterator]() === g); // true(关键:返回自身)
// 3. 因此能被for...of遍历(for...of只遍历done: false的value,忽略return值)
for (let val of g) {
console.log(val); // 1、2(return的值不会被遍历)
}
3. 手动实现迭代器协议(理解 Generator 底层)
Generator 本质是 ES6 帮我们自动实现了迭代器协议,我们手动写一个迭代器,就能明白它的核心逻辑:
// 手动实现一个“模拟Generator”的迭代器
const myIterator = {
_step: 0, // 记录执行步骤
next() {
this._step++;
if (this._step === 1) {
return { value: 1, done: false }; // 对应yield 1
} else if (this._step === 2) {
return { value: 2, done: false }; // 对应yield 2
} else {
return { value: 3, done: true }; // 对应return 3
}
},
// 实现可迭代协议:返回自身
[Symbol.iterator]() {
return this;
}
};
// 调用方式和Generator完全一致
console.log(myIterator.next()); // { value: 1, done: false }
console.log(myIterator.next()); // { value: 2, done: false }
console.log(myIterator.next()); // { value: 3, done: true }
4. 面试高频:迭代器协议的核心考点
- Q:为什么 Generator 对象能被
for...of遍历?
A:因为 Generator 对象符合「可迭代协议」——有[Symbol.iterator]()方法且返回迭代器(自身),而for...of会自动调用迭代器的next()直到done: true。 - Q:迭代器协议和可迭代协议的区别?
A:迭代器协议是“对象有 next() 且返回 {value, done}”,可迭代协议是“对象有 Symbol.iterator 且返回迭代器”;前者是“能一步步取值”,后者是“能被遍历”。
二、co 库:Generator 异步流程的“自动执行器”
1. co 库的核心作用
在 async/await 出现前,Generator 处理异步的最大痛点是「需要手动调用 next()」——co 库的本质是一个自动执行器:它能自动调用 Generator 迭代器的 next(),并把异步操作(Promise)的结果作为参数传入下一个 next(),直到 Generator 执行完毕。
简单说:co 库 = 自动调用 next() + 处理 Promise 结果 + 异常捕获。
2. co 库的基本使用(先看效果)
先安装 co 库:
npm install co
使用示例(对比手动执行和 co 自动执行):
const co = require('co');
// 模拟异步请求
function fetchData(url) {
return new Promise((resolve) => {
setTimeout(() => resolve(`数据:${url}`), 1000);
});
}
// Generator异步函数
function* gen() {
const res1 = yield fetchData('https://api1.com');
const res2 = yield fetchData(`https://api2.com?data=${res1}`);
return res2;
}
// 方式1:手动执行(繁琐)
const g = gen();
g.next().value.then(res1 => {
g.next(res1).value.then(res2 => {
console.log(g.next(res2).value); // 数据:https://api2.com?data=数据:https://api1.com
});
});
// 方式2:co自动执行(简洁)
co(gen).then(res => {
console.log(res); // 数据:https://api2.com?data=数据:https://api1.com
});
3. co 库的核心原理(手写简化版)
co 库的源码不到 200 行,核心逻辑是「递归调用 next() + 处理 Promise」,我们手写一个简化版,吃透它的底层:
// 简化版co库:自动执行Generator
function co(genFunc) {
// 返回Promise,符合现代异步规范
return new Promise((resolve, reject) => {
const g = genFunc(); // 获取生成器迭代器
// 递归执行next的函数
function next(val) {
let result;
try {
result = g.next(val); // 恢复执行,传入上一次异步结果
} catch (e) {
return reject(e); // 捕获Generator内部异常
}
const { value, done } = result;
if (done) {
// 执行完毕,resolve最终值
return resolve(value);
}
// 核心:如果value是Promise,等待resolve后继续next
// 非Promise则直接传入下一个next
Promise.resolve(value).then(
(data) => next(data), // 异步成功:把结果传入下一个next
(err) => g.throw(err) // 异步失败:向Generator内部抛异常
);
}
// 启动执行器
next();
});
}
// 测试:和官方co库效果一致
co(gen).then(res => console.log(res)); // 数据:https://api2.com?data=数据:https://api1.com
4. co 库的核心规则(面试必知)
co 库能自动执行的前提是:Generator 中 yield 后面的值必须是以下类型之一(否则会直接传入 next()):
- Promise(最常用);
- 可迭代对象(Array、Set、Generator 对象等);
- 普通对象/函数(co 会尝试转换为 Promise)。
5. co 库 vs async/await(核心关系)
ES2017 引入的 async/await,本质是「Generator + co 库」的语法糖——浏览器/Node 内置了类似 co 的自动执行器,无需手动引入库。
| 维度 | co + Generator | async/await |
|---|---|---|
| 执行方式 | 手动引入co库 | 语言原生支持,自动执行 |
| 异常处理 | 需配合try/catch + co的catch | 原生try/catch即可 |
| 返回值 | Promise(co返回) | Promise(async函数返回) |
| 语法简洁性 | 稍繁琐(function* + yield) | 更简洁(async + await) |
| 兼容性 | 需ES6环境 + co库 | 需ES2017环境(或babel转译) |
6. 面试高频:co 库的考点
- Q:co 库的作用是什么?
A:co 库是 Generator 函数的自动执行器,核心解决 Generator 处理异步时需要手动调用 next() 的问题;它会自动递归调用 next(),并将 yield 后 Promise 的结果传入下一个 next(),最终返回一个 Promise,简化 Generator 异步流程控制。 - Q:async/await 是不是替代了 co 库?
A:是的。async/await 是 Generator + 自动执行器的语法糖,浏览器/Node 内置了类似 co 的执行逻辑,因此在现代开发中,co 库已几乎被 async/await 取代;但理解 co 库的原理,能更好地理解 async/await 的底层逻辑。
总结
迭代器协议核心
- 迭代器协议:对象有
next()且返回{ value, done };可迭代协议:对象有[Symbol.iterator]()且返回迭代器; - Generator 对象同时满足两套协议,因此能被
for...of遍历、能暂停/恢复; - 迭代器协议是 Generator 实现暂停/遍历的底层规则。
co 库核心
- co 库是 Generator 的自动执行器,核心逻辑是「递归调用 next() + 处理 Promise 结果」;
- co 库解决了 Generator 手动调用 next() 的痛点,是 async/await 的“前身”;
- 现代开发中 async/await 已替代 co 库,但理解 co 库原理是掌握 async/await 底层的关键。
这两个知识点是 Generator 进阶的核心,面试中只要能讲清「迭代器协议的规则」和「co 库的自动执行逻辑」,就能体现你对 Generator 不是只懂表面用法,而是理解底层设计。