嘿,各位观众老爷们,今天咱们来聊聊 JavaScript 里一个相当有趣,但又经常被低估的家伙——Generator 函数。别看它名字里带个“Generator”,就以为它只会生成点数据,实际上它可是个身怀绝技的“协程大师”!
咱们今天的讲座,就围绕着 Generator 函数的高级用法,特别是它如何玩转迭代器协议,以及如何控制协程的流程来展开。准备好了吗?Let’s roll!
第一部分:Generator 函数的基础和迭代器协议
首先,咱们得先搞清楚 Generator 函数长什么样,以及它最基本的用法。
function* myGenerator() {
yield 1;
yield 2;
yield 3;
}
const iterator = myGenerator();
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 }
看到那个 * 了吗?这就是 Generator 函数的标志。yield 关键字则是它的核心武器,它能让函数“暂停”执行,并将 yield 后面的值返回给调用者。下次调用 next() 的时候,函数会从上次暂停的地方继续执行。
迭代器协议
这背后其实隐藏着一个重要的概念——迭代器协议。简单来说,一个对象如果想成为一个迭代器,必须实现一个 next() 方法,这个方法返回一个对象,包含两个属性:
value: 迭代出来的值。done: 一个布尔值,表示迭代是否完成。
Generator 函数返回的对象,恰好就符合这个迭代器协议。所以,我们可以像使用其他迭代器一样使用它。
第二部分:Generator 函数与异步编程
Generator 函数的真正威力,在于它能够简化异步编程。传统的异步编程,回调函数嵌套得像俄罗斯套娃一样,让人头大。而 Generator 函数,配合 yield,可以把异步操作“同步化”。
function fetchData(url) {
return new Promise(resolve => {
setTimeout(() => {
resolve(`Data from ${url}`);
}, 1000);
});
}
function* fetchDataGenerator() {
const data1 = yield fetchData('url1');
console.log(data1);
const data2 = yield fetchData('url2');
console.log(data2);
return 'All done!';
}
function runGenerator(generator) {
const iterator = generator();
function handleNext(value) {
const next = iterator.next(value);
if (next.done) {
return next.value;
} else {
// 如果 yield 返回的是 Promise,就等待 Promise 完成
if (next.value instanceof Promise) {
next.value.then(handleNext);
} else {
handleNext(next.value);
}
}
}
handleNext();
}
runGenerator(fetchDataGenerator);
在这个例子中,fetchDataGenerator 函数通过 yield 暂停执行,等待 fetchData 返回的 Promise 完成。runGenerator 函数负责驱动 Generator 函数的执行,并且处理 yield 返回的 Promise。
这样一来,我们就可以像写同步代码一样写异步代码,避免了回调地狱。
第三部分:Generator 函数与协程
Generator 函数,本质上就是一种协程。协程是一种用户态的线程,它允许你在函数内部暂停和恢复执行,而不需要操作系统的介入。
function* task1() {
console.log('Task 1 started');
yield; // 暂停 Task 1
console.log('Task 1 resumed');
}
function* task2() {
console.log('Task 2 started');
yield; // 暂停 Task 2
console.log('Task 2 resumed');
}
const task1Iterator = task1();
const task2Iterator = task2();
task1Iterator.next(); // Task 1 started
task2Iterator.next(); // Task 2 started
task1Iterator.next(); // Task 1 resumed
task2Iterator.next(); // Task 2 resumed
在这个例子中,task1 和 task2 两个 Generator 函数可以交替执行,就像两个协程一样。
第四部分:Generator 函数的高级用法
接下来,咱们聊点更高级的用法。
- 错误处理
Generator 函数可以使用 try...catch 块来捕获错误。
function* errorGenerator() {
try {
yield 'Starting...';
throw new Error('Something went wrong!');
yield 'Ending...'; // 这行不会执行
} catch (error) {
console.error('Error caught:', error.message);
}
}
const errorIterator = errorGenerator();
console.log(errorIterator.next()); // { value: 'Starting...', done: false }
console.log(errorIterator.next()); // Error caught: Something went wrong!
// { value: undefined, done: true }
errorIterator.next(); //无效果
- 向 Generator 函数传递值
next() 方法可以接受一个参数,这个参数会作为上一个 yield 表达式的返回值。
function* valueGenerator() {
const value = yield 'What is your name?';
console.log('Hello, ' + value + '!');
}
const valueIterator = valueGenerator();
console.log(valueIterator.next()); // { value: 'What is your name?', done: false }
console.log(valueIterator.next('Alice')); // Hello, Alice!
// { value: undefined, done: true }
- 使用
throw()方法
Generator 函数还可以使用 throw() 方法来抛出异常。
function* throwGenerator() {
try {
yield 'Starting...';
} catch (error) {
console.error('Error caught:', error.message);
}
}
const throwIterator = throwGenerator();
console.log(throwIterator.next()); // { value: 'Starting...', done: false }
throwIterator.throw(new Error('Forced error!')); // Error caught: Forced error!
// { value: undefined, done: true }
- 使用
return()方法
Generator 函数可以使用 return() 方法来提前结束迭代。
function* returnGenerator() {
yield 1;
yield 2;
yield 3;
}
const returnIterator = returnGenerator();
console.log(returnIterator.next()); // { value: 1, done: false }
console.log(returnIterator.return('Ending')); // { value: 'Ending', done: true }
console.log(returnIterator.next()); // { value: undefined, done: true }
第五部分:Generator 函数的应用场景
Generator 函数的应用场景非常广泛,以下是一些常见的例子:
- 状态机
Generator 函数可以用来实现状态机。
function* stateMachine() {
let state = 'initial';
while (true) {
switch (state) {
case 'initial':
console.log('State: Initial');
state = yield 'state1';
break;
case 'state1':
console.log('State: State 1');
state = yield 'state2';
break;
case 'state2':
console.log('State: State 2');
return;
}
}
}
const sm = stateMachine();
sm.next(); // State: Initial
sm.next('state1'); // State: State 1
sm.next('state2'); // State: State 2
sm.next(); // 迭代结束
- 数据流处理
Generator 函数可以用来处理数据流。
function* dataStream(data) {
for (const item of data) {
yield item;
}
}
const data = [1, 2, 3, 4, 5];
const stream = dataStream(data);
for (const item of stream) {
console.log(item);
}
- 中间件
Generator 函数可以用来实现中间件。
function* middleware1(next) {
console.log('Middleware 1 before');
yield next;
console.log('Middleware 1 after');
}
function* middleware2(next) {
console.log('Middleware 2 before');
yield next;
console.log('Middleware 2 after');
}
function* finalHandler() {
console.log('Final handler');
}
function compose(middlewares, finalHandler) {
return function() {
let last = finalHandler;
for (let i = middlewares.length - 1; i >= 0; i--) {
last = middlewares[i].bind(null, last);
}
return last();
};
}
const middlewares = [middleware1, middleware2];
const composed = compose(middlewares, finalHandler);
composed();
输出结果:
Middleware 1 before
Middleware 2 before
Final handler
Middleware 2 after
Middleware 1 after
第六部分:Async/Await 与 Generator 函数的关系
ES2017 引入了 async/await 语法,它实际上是 Generator 函数的语法糖。async 函数本质上就是一个返回 Promise 的 Generator 函数,await 关键字相当于 yield 关键字。
async function asyncFunc() {
const data1 = await fetchData('url1');
console.log(data1);
const data2 = await fetchData('url2');
console.log(data2);
return 'All done!';
}
asyncFunc();
这段代码和之前的 fetchDataGenerator 函数的功能是一样的,但是更加简洁易懂。
第七部分:总结
Generator 函数是一个非常强大的工具,它可以用来简化异步编程、实现协程、状态机、数据流处理等等。虽然 async/await 语法更加简洁易懂,但是了解 Generator 函数的原理,可以帮助你更好地理解 async/await 语法,并且在某些场景下,Generator 函数仍然有用武之地。
表格总结
| 特性 | Generator 函数 | Async/Await |
|---|---|---|
| 语法 | function*() { yield ... } |
async function() { await ... } |
| 本质 | 迭代器,协程 | Generator 函数的语法糖 |
| 返回值 | 迭代器对象 | Promise 对象 |
| 异步处理 | 通过 yield 暂停执行,等待 next() 恢复 |
通过 await 暂停执行,等待 Promise 完成 |
| 错误处理 | try...catch 块 |
try...catch 块 |
| 可读性/简洁性 | 相对复杂 | 更加简洁易懂 |
好了,今天的讲座就到这里。希望大家能够掌握 Generator 函数的精髓,并在实际开发中灵活运用。记住,技术是死的,人是活的,不要被语法所束缚,要根据实际情况选择最合适的工具。
下次再见!