嘿,各位观众老爷们,今天咱们来聊聊 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
函数的精髓,并在实际开发中灵活运用。记住,技术是死的,人是活的,不要被语法所束缚,要根据实际情况选择最合适的工具。
下次再见!