[转]JavaScript基础:生成器
ES6 的生成器特性,其他语言里边大多都有这个特性,可用于实现协程,但个人感觉大多数写异步逻辑的场景还是用 Promise 方便,原文链接:juejin.cn/post/6928660813683097613 ,作者:菩提谛听
概述
JavaScript
中函数一旦启动运行,在它结束之前是不会被中断的。但在ES6
中引入了一个全新的函数形式生成器Generator
。这种函数不像普通函数一样保证执行运行到结束,而是具有函数块内暂停和恢复代码执行的能力。生成器Generator
的形式只是在普通函数名称之前加一个*
表示这是一个生成器。其特征主要有两个:1、function
关键字与函数名之间有一个星号;2、函数体内部使用关键字yield
表达式,定义不同的内部状态。生成器Generator
的使用与普通函数一样,只是调用生成器函数会产生一个迭代器,因此可以调用迭代器的next
函数让函数开始或恢复执行。
// 这是一个 Generator
function *genFunc () {
yield 'generator';
yield 'hello';
yield 'word';
return '!';
}
const it = genFunc();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
如下其执行结果就是迭代器产生的内容:
yield
关键字
生成器通过使用yield
关键字便是暂停点。也就是说,生成器函数遇到yield
关键字之前会正常执行,遇到该关键字时,执行会暂停函数的作用域状态会被保留下来,直到调用next()
函数恢复执行。此时的yield
关键字有点像是函数的中间返回语句,它生成的值会出现在next()
返回的对象里。yield
关键字只能用在生成器函数内部,其它地方会抛出错误。
// 普通函数使用 yield
function func () {
yield '测试 yield关键字的使用';
}
yield*
表达式
如何在生成器函数中调用另一个生成器函数,理论上需要在函数体内手动遍历执行另一个生成器函数。
function *gen1 () {
yield 'gen1: hellow';
yield 'gen1: word';
return 'gen1';
}
function *gen2 () {
for (const genValue of gen1()) {
console.log(genValue);
}
yield 'gen2: hellow';
yield 'gen2: word';
return 'gen2';
}
const it = gen2();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
ES6
提供了yield*
表达式即是yield
委托(yield delegation)解决此类问题。yield* ...
需要一个iterable
对象,然后它会调用iterable
的迭代器,把自己的生成器控制委托到这个迭代器,直到其耗尽为止。
function *gen3 () {
const genValue = yield* gen1();
console.log('genValue:', genValue);
yield 'gen3: hellow';
yield 'gen3: word';
return 'gen3';
}
const it = gen3();
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
console.log(it.next());
因此可以使用
yield*
表达式实现递归操作。
function *genMult (x) {
if (x < 3) {
x = yield* genMult(x + 1);
}
return x * 2;
}
const it = genMult(1);
it.next(); // {value: 24, done: true}
yield
输入和输出
yield
既可以作为函数的中间返回语句使用,也可以作为函数的中间参数使用。yield
表达式本身没有返回值,或者说总是返回undefined
。next
方法可以带一个参数,该参数就会被当作上一个yield
表达式的返回值。
function *sum (x) {
const y = yield;
const z = yield;
return x + y + z;
}
const it = sum(1);
console.log(it.next());
console.log(it.next(2));
console.log(it.next(3));
其中第一次调用
next
出入的值不会被使用,因为第一次调用是为了开始执行生成器函数。
提前终止生成器
与迭代器类似可以使用return
和throw
提前结束跌倒器的状态,生成器也是可以使用return
和throw
方法提前终止生成器。return
方法会强制生成器进入关闭状态,return
方法传入的值,就是终止生成器的值。切后续关闭状态无法恢复,后续再次调用next
方法done: true
会一直保持。
function *returnFunc () {
yield 1;
yield 2;
yield 3;
}
const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());
return
方法除了关闭生成器之外,如何生成器函数中包含finally
子句,还可以执行情路任务如何资源释放、状态重置等。也就是说如果生成器函数内部有try...finally
代码块,且正在执行try
代码块,那么return()
方法会导致立刻进入finally
代码块,执行完以后,整个函数才会结束。
function *returnFunc () {
try {
yield 1;
yield 2;
} finally {
yield 3;
yield 5;
}
}
const it = returnFunc();
console.log(it.next());
console.log(it.return(4));
console.log(it.next());
console.log(it.next());
注意调用
return()
方法后,就立即执行finally
代码块,不执行try
里余下的代码了,然后等到finally
代码块执行完,再返回return()
方法指定的值。
throw
方法会在暂停的时候将一个提供的错误注入到生成器对象中,相当于在暂停点出入一个throw ...
。如果生成器内部有catch
模块对错误进行处理,那么生成器就不会关闭,而且还可以恢复执行。
function *throwFunc () {
try {
yield 1;
yield 2;
} catch (e) {
console.error(e);
yield 3;
yield 5;
}
}
const it = throwFunc();
console.log(it.next());
console.log(it.throw(new Error('throw error')));
console.log(it.next());