生成器与协程
生成器的基本概念
生成器是JavaScript中一种特殊的函数,它可以通过function*
语法定义。与普通函数不同,生成器函数可以暂停执行并在稍后恢复,这种特性使得它非常适合处理异步操作和迭代场景。生成器函数执行时不会立即运行函数体,而是返回一个生成器对象。
function* simpleGenerator() {
yield 1;
yield 2;
yield 3;
}
const gen = simpleGenerator();
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
生成器通过yield
关键字暂停函数执行并返回一个值。每次调用生成器的next()
方法时,函数会从上次暂停的位置继续执行,直到遇到下一个yield
或函数结束。
生成器的内部机制
生成器函数在调用时不会立即执行函数体,而是返回一个实现了迭代器协议的生成器对象。这个对象维护着函数的执行上下文,包括局部变量、参数和当前的执行位置。当调用next()
方法时,生成器会恢复执行直到遇到yield
表达式。
function* counterGenerator() {
let count = 0;
while(true) {
count += 1;
yield count;
}
}
const counter = counterGenerator();
console.log(counter.next().value); // 1
console.log(counter.next().value); // 2
console.log(counter.next().value); // 3
生成器还可以通过yield*
表达式委托给另一个生成器,这在组合多个生成器时非常有用:
function* generatorA() {
yield 'a';
yield 'b';
}
function* generatorB() {
yield* generatorA();
yield 'c';
yield 'd';
}
const gen = generatorB();
console.log([...gen]); // ['a', 'b', 'c', 'd']
协程的概念与实现
协程是一种比线程更轻量级的并发编程模型,它允许在单个线程内实现多个执行流的协作式调度。在JavaScript中,生成器可以看作是协程的一种实现方式,因为它们允许函数在执行过程中暂停和恢复。
协程与线程的主要区别在于:
- 协程是协作式的,需要显式地让出控制权
- 协程在用户空间实现,不依赖操作系统调度
- 协程切换开销远小于线程切换
function* coroutineA() {
console.log('A1');
yield;
console.log('A2');
yield;
console.log('A3');
}
function* coroutineB() {
console.log('B1');
yield;
console.log('B2');
yield;
console.log('B3');
}
const a = coroutineA();
const b = coroutineB();
a.next(); // A1
b.next(); // B1
a.next(); // A2
b.next(); // B2
a.next(); // A3
b.next(); // B3
生成器与异步编程
生成器在Node.js中常用于简化异步代码的编写。结合Promise和生成器,可以实现类似同步代码风格的异步流程控制。这种模式在Koa框架中被广泛使用。
function asyncTask(value) {
return new Promise(resolve => {
setTimeout(() => resolve(value * 2), 1000);
});
}
function* asyncGenerator() {
const result1 = yield asyncTask(1);
console.log(result1); // 2
const result2 = yield asyncTask(2);
console.log(result2); // 4
return result1 + result2;
}
function runGenerator(gen) {
const iterator = gen();
function iterate(iteration) {
if (iteration.done) return Promise.resolve(iteration.value);
return Promise.resolve(iteration.value)
.then(x => iterate(iterator.next(x)))
.catch(e => iterate(iterator.throw(e)));
}
return iterate(iterator.next());
}
runGenerator(asyncGenerator).then(result => {
console.log('Final result:', result); // 6
});
生成器与迭代器协议
生成器天然实现了迭代器协议,这使得它们可以无缝地与JavaScript的迭代语法配合使用。任何期望可迭代对象的地方都可以使用生成器。
function* fibonacci() {
let [prev, curr] = [0, 1];
while (true) {
yield curr;
[prev, curr] = [curr, prev + curr];
}
}
// 使用for...of循环
for (const n of fibonacci()) {
if (n > 1000) break;
console.log(n);
}
// 与数组解构配合
const [first, second, third] = fibonacci();
console.log(first, second, third); // 1 1 2
生成器的高级用法
生成器不仅可以产生值,还可以通过next()
方法接收外部输入。这使得生成器可以实现双向通信,进一步增强了它们的灵活性。
function* twoWayGenerator() {
const name = yield 'What is your name?';
const age = yield `Hello ${name}, how old are you?`;
return `${name} is ${age} years old`;
}
const gen = twoWayGenerator();
console.log(gen.next().value); // "What is your name?"
console.log(gen.next('Alice').value); // "Hello Alice, how old are you?"
console.log(gen.next(30).value); // "Alice is 30 years old"
生成器还可以用于实现状态机,将复杂的状态转换逻辑封装在生成器函数中:
function* trafficLight() {
while (true) {
yield 'red';
yield 'yellow';
yield 'green';
yield 'yellow';
}
}
const light = trafficLight();
console.log(light.next().value); // 'red'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'green'
console.log(light.next().value); // 'yellow'
console.log(light.next().value); // 'red'
Node.js中的实际应用
在Node.js生态系统中,生成器被广泛用于各种场景。Koa框架就是基于生成器构建的,它利用生成器来处理中间件流程。
const Koa = require('koa');
const app = new Koa();
app.use(function* (next) {
const start = Date.now();
yield next;
const ms = Date.now() - start;
this.set('X-Response-Time', `${ms}ms`);
});
app.use(function* (next) {
const start = Date.now();
yield next;
const ms = Date.now() - start;
console.log(`${this.method} ${this.url} - ${ms}`);
});
app.use(function* () {
this.body = 'Hello World';
});
app.listen(3000);
生成器也常用于处理数据流,特别是在处理大型数据集时,可以有效地控制内存使用:
const fs = require('fs');
const readline = require('readline');
function* readLines(filePath) {
const fileStream = fs.createReadStream(filePath);
const rl = readline.createInterface({
input: fileStream,
crlfDelay: Infinity
});
for await (const line of rl) {
yield line;
}
}
async function processLargeFile() {
const lineReader = readLines('large-file.txt');
for (let i = 0; i < 10; i++) {
const { value } = await lineReader.next();
console.log(value);
}
}
processLargeFile();
生成器与协程的性能考量
虽然生成器提供了强大的控制流抽象,但它们也有一些性能上的考虑。生成器的创建和切换比普通函数调用开销更大,因此在性能关键的代码路径中需要谨慎使用。
Node.js的V8引擎对生成器进行了优化,但在大规模使用时仍需注意:
- 生成器对象的创建和销毁会有额外开销
- 生成器切换比函数调用慢约2-5倍
- 在热代码路径中频繁使用生成器可能影响性能
// 性能测试:生成器 vs 普通函数
function normalFunction(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += i;
}
return sum;
}
function* generatorFunction(n) {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += i;
yield;
}
return sum;
}
console.time('normal');
normalFunction(1e6);
console.timeEnd('normal'); // 约1-3ms
console.time('generator');
const gen = generatorFunction(1e6);
let result;
while (!(result = gen.next()).done) {}
console.timeEnd('generator'); // 约10-30ms
生成器的错误处理
生成器提供了完善的错误处理机制,可以通过throw()
方法将错误注入到生成器内部。这使得生成器可以像同步代码一样处理错误。
function* errorHandlingGenerator() {
try {
yield 'Step 1';
yield 'Step 2';
throw new Error('Something went wrong');
yield 'Step 3';
} catch (err) {
console.log('Caught error:', err.message);
yield 'Recovery step';
} finally {
console.log('Cleaning up');
yield 'Cleanup step';
}
}
const gen = errorHandlingGenerator();
console.log(gen.next().value); // "Step 1"
console.log(gen.next().value); // "Step 2"
console.log(gen.throw(new Error('Injected error')).value); // "Recovery step"
console.log(gen.next().value); // "Cleanup step"
生成器与递归
生成器可以很好地处理递归算法,特别是那些需要惰性求值或生成无限序列的场景。生成器递归通常使用yield*
语法来实现。
function* traverseTree(node) {
if (!node) return;
yield node.value;
yield* traverseTree(node.left);
yield* traverseTree(node.right);
}
const tree = {
value: 1,
left: {
value: 2,
left: { value: 4 },
right: { value: 5 }
},
right: {
value: 3,
left: { value: 6 },
right: { value: 7 }
}
};
console.log([...traverseTree(tree)]); // [1, 2, 4, 5, 3, 6, 7]
对于更复杂的递归场景,生成器可以避免栈溢出问题,因为它们可以逐步产生结果而不需要一次性计算所有值:
function* fibonacciSequence(n, prev = 0, curr = 1) {
if (n <= 0) return;
yield curr;
yield* fibonacciSequence(n - 1, curr, prev + curr);
}
console.log([...fibonacciSequence(10)]); // [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
生成器与并发模式
生成器可以用于实现各种并发模式,如生产者-消费者模型、管道处理和并行任务协调。这些模式在Node.js的I/O密集型应用中特别有用。
function* producer(items) {
for (const item of items) {
console.log('Producing', item);
yield item;
}
}
function* consumer() {
let count = 0;
while (true) {
const item = yield;
console.log('Consuming', item);
count++;
if (count >= 5) break;
}
}
function runPipeline() {
const items = [1, 2, 3, 4, 5, 6, 7, 8];
const prod = producer(items);
const cons = consumer();
cons.next(); // 启动消费者
for (const item of prod) {
cons.next(item); // 将每个项目传递给消费者
}
}
runPipeline();
对于更复杂的并发控制,可以结合Promise和生成器实现协程池:
async function runInPool(generators, poolSize) {
const executing = new Set();
const results = [];
for (const gen of generators) {
const promise = Promise.resolve().then(() => gen.next().value);
executing.add(promise);
const cleanUp = () => executing.delete(promise);
promise.then(cleanUp).catch(cleanUp);
if (executing.size >= poolSize) {
await Promise.race(executing);
}
}
return Promise.all(executing);
}
const tasks = [
function*() { yield asyncTask(1); return 1; },
function*() { yield asyncTask(2); return 2; },
function*() { yield asyncTask(3); return 3; },
function*() { yield asyncTask(4); return 4; },
function*() { yield asyncTask(5); return 5; }
];
runInPool(tasks, 2).then(console.log); // [1, 2, 3, 4, 5]
生成器的替代方案
虽然生成器功能强大,但在现代JavaScript中,async/await语法通常更简洁地解决了大多数异步编程问题。了解生成器与async/await的关系有助于更好地选择工具。
// 使用生成器
function* oldWay() {
const a = yield Promise.resolve(1);
const b = yield Promise.resolve(2);
return a + b;
}
// 使用async/await
async function newWay() {
const a = await Promise.resolve(1);
const b = await Promise.resolve(2);
return a + b;
}
然而,生成器仍然在某些场景下具有优势:
- 需要精细控制执行流程时
- 实现自定义迭代逻辑时
- 需要双向通信的协程时
- 处理非Promise的异步值时
// 生成器处理非Promise异步值
function* eventGenerator() {
const clicks = yield (next) => {
document.addEventListener('click', next);
return () => document.removeEventListener('click', next);
};
return `Clicked at ${clicks.clientX}, ${clicks.clientY}`;
}
function runEventGenerator(gen) {
const iterator = gen();
let cleanup;
function step(value) {
const result = iterator.next(value);
if (result.done) return result.value;
if (typeof result.value === 'function') {
cleanup = result.value(step);
} else {
Promise.resolve(result.value).then(step);
}
}
step();
return () => cleanup && cleanup();
}
const stop = runEventGenerator(eventGenerator);
// 点击页面后会打印坐标
setTimeout(stop, 5000); // 5秒后停止监听
本站部分内容来自互联网,一切版权均归源网站或源作者所有。
如果侵犯了你的权益请来信告知我们删除。邮箱:cc@cccx.cn
上一篇:发布/订阅模式
下一篇:单字段索引与复合索引