2024-12-12    2024-12-12    1492 字  3 分钟

在 Node.js 中,generator 是一种特殊的函数,它允许你通过 yield 关键字暂停和恢复函数的执行。generator 函数使用 function* 语法来定义,并且返回一个 Generator 对象。这个 Generator 对象可以通过 next() 方法来控制函数的执行流程。

基础

基本用法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
function* myGenerator() {
  yield 1;
  yield 2;
  yield 3;
}

const gen = myGenerator();

console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

// 也可以使用 for...of 循环迭代 generator 对象
// for (let res of gen) {
//     console.log(res);
// }
// 1
// 2
// 3

如上所示,关键点有:

  1. function* 语法: 定义一个 generator 函数。
  2. yield 关键字: 暂停函数的执行,并返回一个值。
  3. next() 方法: 恢复函数的执行,并返回一个对象,包含 valuedone 两个属性。
    • value: 当前 yield 返回的值。
    • done: 表示 generator 函数是否已经执行完毕。

当执行到 donetrue 时,这个generator对象就已经全部执行完毕,不要再继续调用 next() 了。我们还可以直接用 for...of 循环迭代 generator 对象,这种方式不需要我们自己判断 done 了。

传递参数 🌟

你可以通过 next() 方法向 generator 函数传递参数,这些参数会被当作上一次 yield 表达式的返回值。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
function* myGenerator() {
  const x = yield 1;
  console.log(x);          // 输出: 42
  yield x;
}

const gen = myGenerator();

console.log(gen.next());   // { value: 1, done: false }
console.log(gen.next(42)); // { value: 42, done: false }
console.log(gen.next());   // { value: undefined, done: true }

错误处理

generator 函数可以通过 try...catch 来捕获错误。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function* myGenerator() {
  try {
    yield 1;
    yield 2;
  } catch (e) {
    console.log('Caught error:', e);
  }
}

const gen = myGenerator();

console.log(gen.next());                       // { value: 1, done: false }
gen.throw(new Error('Something went wrong'));  // 输出: Caught error: Error: Something went wrong
console.log(gen.next());                       // { value: undefined, done: true }

与异步操作结合

generator 函数可以与异步操作结合使用,通常与 co 库或 async/await 一起使用。虽然 async/await 是更现代的解决方案,但在某些情况下,generator 仍然有用。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const co = require('co');

function* myGenerator() {
  const result1 = yield Promise.resolve(1);
  const result2 = yield Promise.resolve(2);
  return result1 + result2;
}

co(myGenerator).then(result => {
  console.log(result); // 输出: 3
});

总结

generator 是一种强大的工具,允许你以一种更灵活的方式控制函数的执行流程。虽然 async/await 已经成为处理异步操作的主流方式,但 generator 仍然在某些场景下非常有用,尤其是在需要手动控制执行流程的情况下。

附录

CO 库是什么

co 是一个基于 generator 的异步流程控制库,由 TJ Holowaychuk 开发。它允许你使用 generator 函数来编写看起来像同步代码的异步代码,从而简化异步编程。co 的核心思想是将 generator 函数与 Promise 结合,自动处理异步操作的执行流程。

你可以通过 npm 安装 co

1
npm install co

基本用法

co 接受一个 generator 函数作为参数,并返回一个 Promiseco 会自动执行 generator 函数中的 yield 表达式,并将其结果包装为 Promise

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const co = require('co');

co(function* () {
  const result1 = yield Promise.resolve(1);
  const result2 = yield Promise.resolve(2);
  return result1 + result2;
}).then(result => {
  console.log(result); // 输出: 3
}).catch(err => {
  console.error(err);
});

支持的 yield 类型

co 支持多种类型的 yield 表达式:

  1. Promise: co 会等待 Promise 完成,并返回其结果。
  2. Thunk: 一个返回单个参数函数的函数(类似于回调函数)。
  3. Array: 并行执行多个 Promise,返回一个包含所有结果的数组。
  4. Object: 并行执行多个 Promise,返回一个包含所有结果的对象。

示例:并行执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
co(function* () {
  const [result1, result2] = yield [
    Promise.resolve(1),
    Promise.resolve(2)
  ];
  return result1 + result2;
}).then(result => {
  console.log(result); // 输出: 3
}).catch(err => {
  console.error(err);
});

示例:对象并行执行

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
co(function* () {
  const results = yield {
    a: Promise.resolve(1),
    b: Promise.resolve(2)
  };
  return results.a + results.b;
}).then(result => {
  console.log(result); // 输出: 3
}).catch(err => {
  console.error(err);
});

错误处理

co 会自动捕获 generator 函数中的错误,并将其传递给 Promisecatch 方法。

1
2
3
4
5
6
7
8
co(function* () {
  const result = yield Promise.reject(new Error('Something went wrong'));
  return result;
}).then(result => {
  console.log(result);
}).catch(err => {
  console.error(err.message); // 输出: Something went wrong
});

async/await 的对比

co 的功能与 async/await 非常相似,但 async/await 是原生支持的语法,而 co 是基于 generator 的库。以下是两者的一个简单对比:

co 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const co = require('co');

co(function* () {
  const result1 = yield Promise.resolve(1);
  const result2 = yield Promise.resolve(2);
  return result1 + result2;
}).then(result => {
  console.log(result); // 输出: 3
}).catch(err => {
  console.error(err);
});

async/await 示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
async function myAsyncFunction() {
  const result1 = await Promise.resolve(1);
  const result2 = await Promise.resolve(2);
  return result1 + result2;
}

myAsyncFunction().then(result => {
  console.log(result); // 输出: 3
}).catch(err => {
  console.error(err);
});

总结

co 是一个非常强大的库,它通过 generatorPromise 的结合,简化了异步编程的复杂性。虽然 async/await 已经成为现代 JavaScript 中处理异步操作的主流方式,但 co 仍然在某些场景下非常有用,尤其是在需要与旧代码或特定库兼容的情况下。