ES 6 系列之三种异步 - Generator 函数

介绍

GeneratorES 6 提供的一种异步编程解决方案。

  • 函数定义时,在 funciton 后面添加一个星号(*)
  • 函数体内部使用 yield 表达式
1
2
3
4
5
6
7
8
9
// 普通函数
function a() {}

// Generator
function* generator() {
yield 'hello';
yield 'hzzzzzzzq';
return 'good';
}

Generator 函数的调用方式与普通函数是一样的,在函数名后面加上一对圆括号。

yield 表达式

yield 表达式就是 Generator 的暂停标志。

  • 遇到 yield 表达式,就会停止执行后面的操作,yield 后面表达式的值,作为返回对象的 value 属性值
  • 下次调用 next 方法时,才会继续执行代码,直到下一个 yield
  • 如果没有遇到遇到 yield,则运行至结束,看是否有 return,如果有,则 return 后面的值作为返回对象的 value
  • 如果没有 yieldreturn,则返回 valueundefined
1
2
3
4
5
function* generator() {
yield 'hello'; // 第一个 next 时,执行
yield 'hzzzzzzzq'; // 第二个 next 时,执行
return 'good'; // 第三个 next 时,执行
}

在上面的代码中,我们可以来感受上面的用途。

只有在调用 next 方法将指针移到这一句时,才会进行求值。

return 不同的地方在于,return 不具备记忆功能,也就说,在一个函数中,return 只能执行一次,而 yield 具备记忆功能,可以通过 next 方法继续执行代码。

next 方法

next 普通使用

next 方法就是 generator 函数的内置执行器,如果遇到 yield 时,只有通过调用 next,才会继续执行代码。

next 代码的执行值,我们可以打印来看,会是一个对象,带有 valuedone 两个属性。

1
2
3
4
5
6
7
8
9
10
11
function* generator() {
yield 'hello';
yield 'hzzzzzzzq';
return 'good';
}

const gen = generator();
console.log(gen.next()); // { value: 'hello', done: false }
console.log(gen.next()); // { value: 'hzzzzzzzq', done: false }
console.log(gen.next()); // { value: 'good', done: true }
console.log(gen.next()); // { value: undefined, done: true }
  • value 值显示的是 yield 或者 return 后面的返回值。
  • done 是一个布尔值,表示遍历是否结束,true 表示结束。
  • 当然,return 只能有一个,且 return 之后不能有 yield,因为 return,就会让 done 变成 true

next 方法参数

next 方法可以带一个参数,该参数会被当作上一个 yield 表达式的值

1
2
3
4
5
6
7
8
9
10
11
12
function* generator() {
while (true) {
let value = yield null;
console.log(value);
}
}
const gen = generator();
console.log(gen.next());
console.log(gen.next('world'));
// { value: null, done: false }
// world
// { value: 'null', done: false }

当然,这个是会被当作上一个 yield 表达式的值,是在函数内部使用,并不是在 next 返回对象中作为 value 的键值返回。他会替换 generator 函数体内的上一个 yield 值并赋值给我们定义的变量 value

for…of 循环

for...of 循环可以自动遍历 Generator 函数运行时生成的 Interator 对象,不需要手动调用 next 方法。

1
2
3
4
5
6
7
8
9
10
11
function* gen() {
yield 'one';
yield 'two';
yield 'three';
yield 'four';
return 'five';
}
for (let v of gen()) {
console.log(v);
}
// one two three four

注意:如果 donetrue,则会被立刻终止,所有最后一句 return 语句不在循环中。

处理 for...of 循环之外,扩展运算符,解构赋值和 Array.from 方法内部调用的,都是遍历器接口。
都可以将 Generator 函数作为返回的 Interator 对象,作为参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
function* gen() {
yield 'one';
yield 'two';
yield 'three';
yield 'four';
return 'five';
}
console.log([...gen()]);
// ['one', 'two', 'three', 'four']

console.log(Array.from(gen()));
// ['one', 'two', 'three', 'four']

let [x, y, z] = gen();
console.log(x, y, z);
// one two three

yield*

yield* 就是 用来在一个 Generator 函数里面执行另一个 Generator 函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function* generator1() {
yield 'hello';
yield 'hzzzzzzzq';
yield 'good';
}
function* generator() {
yield* generator1();
}
const gen = generator();
console.log(gen.next().value); // hello
console.log(gen.next().value); // hzzzzzzzq
console.log(gen.next().value); // good
console.log(gen.next().value); // undefined

我们在 generator 函数中,使用了 yield* ,后面调用 generator1 函数。

然后我们就可以直接使用 next 函数,跟普通的 generator 函数一样使用就可以。

Generator.prototype.throw() / return()

throw()

Generator 函数返回的遍历器对象,都会有个 throw 方法,可以在函数体外抛出错误,然后在 Generator 函数体内捕获。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function* generator() {
try {
yield 1;
} catch (e) {
console.log('内部', e);
}
}
let gen = generator();
gen.next();
try {
gen.throw('错误1');
gen.throw('错误2');
} catch (e) {
console.log('外部', e);
}
// 内部 错误1
// 外部 错误2

在上面的例子中,我们可以看出,第一个 throw 被函数体内部的 catch 语句捕获,然而第二个错误,在内部已经执行过 catch,所有这个错误就被抛出来了,被外面的 try...catch 捕获。

return()

Generator 函数返回的对象中,还有一个 return 方法,可以返回给定的值,并且结束 Generator 函数。

1
2
3
4
5
6
7
8
9
function* generator() {
yield 'hello';
yield 'hzzzzzzzq';
yield 'good';
}
const gen = generator();
gen.next(); // { value: 'hello', done: false }
gen.return('world'); // { value: 'world', done: true }
gen.next(); // { value: undefined, done: true }

从上面的例子,我们可以看出,执行 return 之后,后面的 done 就会变为 true,且传入的参数会作为返回对象的 value 值。

在以后调用时,都会被调整为 undefined,其实就相当于,将 generator 函数中的添加一个 return

Generator 函数的异步操作

我们使用一个 readFile,使用 setTimeout 假装调用接口,然后调用 gen.next() 接口返回对象的 value 属性值进行下一步操作。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
const readFile = function (fileName, ms) {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve('异步请求成功回调'); // 需要给 `Promise` 一个状态转化,就是异步执行是否成功
}, ms);
});
};

function* generator() {
const str = yield readFile('fileName1', 1000);
console.log(str);
}
const gen = generator();
const result = gen.next(); // fileName1
result.value
.then((val) => {
console.log(val);
return val;
})
.then((val2) => {
gen.next('异步请求结果');
});
// 异步请求成功回调
// 异步请求结果

yield 后面用来调用异步函数,然后返回结果输出。

-------------------- 本文结束 感谢阅读 --------------------