1. 什么是Generator函数
Generator函数是ES6 新增的一种函数类型,它也是一种迭代器,可以用来生成一个值序列。与普通函数不同,Generator函数可以在执行过程中返回多次结果,每次返回结果后,Generator函数的执行状态被暂停,直到下一次调用它的时候,从暂停的位置继续往后执行。Generator函数使用函数关键字 function 后面跟着一个*号来定义。
function* myGenerator() {
yield 'hello';
yield 'world';
yield '!';
}
const gen = myGenerator();
console.log(gen.next().value); // 'hello'
console.log(gen.next().value); // 'world'
console.log(gen.next().value); // '!'
1.1 yield表达式
yield是Generator函数内部的一种暂停标志,每次调用next方法,Generator函数会从上一次暂停的位置继续执行,直到遇到下一个yield表达式,再次暂停。通过yield表达式,Generator函数实现了代码的暂停和恢复。
以下Generator函数中,每次调用next方法,函数都会从上一个yield表达式开始运行,直到遇到下一个yield表达式就暂停执行,返回一个结果。
function* myGenerator() {
const a = yield 1;
console.log(a);
const b = yield 2;
console.log(b);
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next('hello')); // 'hello' { value: 2, done: false }
console.log(gen.next('world')); // 'world' { value: undefined, done: true }
1.2 yield表达式的返回值
调用Generator函数时,可以向函数内部传入一个参数,该参数即为yield表达式的返回值。每个yield表达式都可以返回不同的值,通过.next()方法可以获取到Generator函数每次返回的值。
function* myGenerator() {
const a = yield 1;
const b = yield 2;
const c = yield 3;
yield a + b + c;
}
const gen = myGenerator();
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next(2)); // { value: 2, done: false }
console.log(gen.next(3)); // { value: 3, done: false }
console.log(gen.next(4)); // { value: 9, done: false }
console.log(gen.next()); // { value: undefined, done: true }
2. Generator函数应用
2.1 异步操作的同步化表达
Generator函数的主要应用之一就是异步操作同步化表达。在Generator函数内,异步操作可以像同步操作一样被执行。以下Generator函数通过yield表达式实现异步回调函数的同步执行。
function* myGenerator() {
const res = yield fetch('https://jsonplaceholder.typicode.com/todos/1');
const data = yield res.json();
console.log(data.title);
}
const gen = myGenerator();
const promise = gen.next().value;
promise.then((res) => {
return gen.next(res).value;
}).then((data) => {
gen.next(data);
});
2.2 实现遍历器接口
Generator函数还可以用来实现一个可遍历对象的接口,JavaScript中的数组、Set对象、Map对象、DOM NodeList对象、字符串等,都是可遍历对象。只要具有Symbol.iterator属性,就可以使用for...of语句进行遍历。以下Generator函数可以迭代一个对象的所有属性。
const obj = { a: 1, b: 2, c: 3 };
obj[Symbol.iterator] = function* () {
const keys = Object.keys(obj);
for (let key of keys) {
yield obj[key];
}
};
for (let v of obj) {
console.log(v);
}
2.3 数据流管道
通过生成器的管道功能,可以将多个操作连结成一条管道来处理数据,类似于UNIX系统中的管道操作,只不过数据流管道在JavaScript中是使用Generator函数实现的。
以下示例中,compose函数将三个Generator函数链接起来,实现了将数据加倍、过滤掉奇数、累加求和的功能。这里每次向函数中输入两个数,经过三个函数处理之后,最终得到结果3。通过这种方式,可以将多个函数整合成一条管道来处理数据,提高代码的可读性、可维护性。
function* filterOdd() {
while(true) {
const n = yield;
if (n % 2 === 0) {
yield n;
}
}
}
function* double() {
while(true) {
const n = yield;
yield n * 2;
}
}
function* sum() {
let result = 0;
while(true) {
const n = yield;
result += n;
yield result;
}
}
function* compose(...gens) {
let lastInput = null;
let lastOutput = null;
for (let i = 0; i < gens.length; i++) {
const gen = gens[i]();
if (lastOutput) {
lastOutput = gen.next(lastInput).value;
} else {
lastOutput = gen.next().value;
}
lastInput = lastOutput;
}
yield lastOutput;
}
const gen = compose(filterOdd, double, sum);
gen.next(); // 必须在第一次执行生成器函数之前调用一次next方法
gen.next(1); // 第一组输入 1
gen.next(2); // 第二组输入 2
gen.next(3); // 第三组输入 3
console.log(gen.next().value); // 输出 3
3. 总结
本文主要介绍了Generator函数中yield表达式的作用和应用场景,通过实例演示了Generator函数异步操作同步化表达、可遍历对象接口实现、数据流管道等应用场景。Generator函数具有以下特点:
可以返回多次结果
可以暂停执行,并从上一次暂停的位置继续执行
可以向函数内部传入参数,并通过yield表达式返回值
使用Generator函数可以简化异步编程的复杂度,提高代码的可读性、可维护性,具有很好的应用价值。