JavaScript中Generator函数yield表达式示例详解

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函数可以简化异步编程的复杂度,提高代码的可读性、可维护性,具有很好的应用价值。