基本介绍
生成器是 JavaScript 中的一种特殊函数,能够在执行过程中暂停,并且在需要的时候恢复执行。这与普通函数的执行方式不同,普通函数一旦开始执行,必须执行完所有语句后才会返回,而生成器可以在执行过程中多次暂停。
1. 基本语法
生成器函数由 function* 定义,内部使用 yield 关键字来暂停执行。每次调用生成器函数时,它不会立即执行代码,而是返回一个生成器对象(Generator 对象)。通过调用这个对象的 next() 方法,才会逐步执行代码,直到遇到 yield 关键字。
//myGenerator 函数是一个生成器函数,它在执行过程中依次 yield 出 1、2、3。
//每次 yield 表示函数暂停,等待下一次 next() 调用来继续执行。
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 }
//next() 方法:
//value: 生成器 yield 出的值。
//done: 当生成器遍历完成时为 true,否则为 false。
//生成器的执行流程可以通过 next() 方法逐步推进,函数执行到 yield 时暂停,
//等到下次调用 next() 时继续执行。
生成器与迭代器(Iterators)的关系
迭代器是 JavaScript 中一种用于遍历数据集合(如数组、字符串)的对象,它有一个 next() 方法,每次调用 next() 会返回集合中的下一个值。生成器实际上是一个实现了迭代器协议的对象,它天生就是迭代器!
1. 迭代器的原理
迭代器对象需要具有一个 next() 方法,next() 方法返回一个对象,包含:
value: 当前的值。
done: 一个布尔值,指示迭代是否完成。
手动创建迭代器的例子:
function createIterator(arr) {
let index = 0;
return {
next: function() {
if (index < arr.length) {
return { value: arr[index++], done: false };
} else {
return { value: undefined, done: true };
}
}
};
}
const iterator = createIterator([1, 2, 3]);
console.log(iterator.next()); // { value: 1, done: false }
console.log(iterator.next()); // { value: 2, done: false }
console.log(iterator.next()); // { value: 3, done: false }
console.log(iterator.next()); // { value: undefined, done: true }
生成器作为迭代器
生成器函数自动实现了迭代器协议,不需要手动编写 next() 方法。生成器非常方便地创建可迭代对象。
使用生成器代替手动迭代器:
function* createGenerator(arr) {
for (let i = 0; i < arr.length; i++) {
yield arr[i];
}
}
const gen = createGenerator([1, 2, 3]);
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 }
//实际上,所有的生成器都是迭代器!
生成器与 Promise 结合
生成器的暂停和恢复机制使得它可以与 Promise 结合起来,处理异步代码。这种模式被称为"协程"(coroutine),它使得异步代码看起来更像同步代码。
1. 使用生成器处理异步操作
假设我们有一个 Promise 返回的异步函数:
function fetchData() {
return new Promise((resolve) => {
setTimeout(() => resolve("数据加载完成"), 1000);
});
}
//通常我们会通过 .then() 或者 async/await 来处理这个 Promise。
//然而,生成器也可以用来处理异步操作,配合 next() 和 yield 来暂停和恢复执行
function* asyncGenerator() {
console.log("开始加载数据...");
const data = yield fetchData(); // 暂停,等待数据加载完成
console.log(data); // 数据加载完成
}
//生成器的 yield 可以等待 Promise 的结果返回。
//在 yield 后,生成器暂停,直到我们手动恢复执行。
手动执行生成器处理 Promise
要执行上面的生成器并让它处理异步操作,我们可以手动编写控制逻辑:
const gen = asyncGenerator();
const result = gen.next(); // 开始加载数据...
result.value.then((data) => {
gen.next(data); // 恢复生成器的执行,传入异步操作结果
});
//通过调用 gen.next(),生成器暂停在 yield 处,等待异步操作完成。
//异步操作完成后,通过 gen.next(data) 继续执行,并把数据传递回生成器。
自动执行生成器与 Promise 的结合
为了简化手动调用生成器,我们可以编写一个自动执行生成器的函数:
function runGenerator(genFunc) {
const gen = genFunc();
function handleNext(nextValue) {
const result = gen.next(nextValue);
if (!result.done) {
result.value.then(handleNext); // 等待 Promise 完成,再继续执行
}
}
handleNext(); // 启动生成器
}
runGenerator(asyncGenerator);
//这个 runGenerator 函数能够自动执行生成器中的异步操作,无需手动调用 next(),类似于 //async/await 的效果
生成器与 async/await
生成器与 async/await 非常相似,都是用来处理异步代码的工具。async/await 其实是生成器的更高级抽象,专门用于处理 Promise。async 函数内部使用的是生成器的思想,只不过 JavaScript 通过引擎帮我们自动管理了 next() 的调用。
对比:
生成器:需要手动调用 next() 处理异步逻辑。
async/await:是生成器的封装,自动处理 next() 的调用,代码更简洁。
生成器版本
function* asyncGenerator() {
const data = yield fetchData();
console.log(data);
}
async/await版
async function asyncFunction() {
const data = await fetchData();
console.log(data);
}