描述
生成器函数在执行时能暂停,后面又能从暂停处继续执行。
调用一个生成器函数并不会马上执行它里面的语句,而是返回一个这个生成器的 迭代器 ( iterator )对象。当这个迭代器的 next() 方法被首次(后续)调用时,其内的语句会执行到第一个(后续)出现yield的位置为止,yield 后紧跟迭代器要返回的值。或者如果用的是 yield*(多了个星号),则表示将执行权移交给另一个生成器函数(当前生成器暂停执行)。
next()方法返回一个对象,这个对象包含两个属性:value 和 done,value 属性表示本次 yield 表达式的返回值,done 属性为布尔类型,表示生成器后续是否还有 yield 语句,即生成器函数是否已经执行完毕并返回。
输入和输出
生成器对象是由一个 generator function 返回的,并且它符合可迭代协议和迭代器协议,是一个特殊的函数,具有新的执行模式。但是它仍然是一个函数,这意味着它仍然有一些基本的特性没有改变。比如,它仍然可以接受参数(输入),也能够返回值(输出)。
function *foo(x,y){
return x * y;
}
const it = foo(6,7);
const res = it.next();
console.log(res.value); // 42
- 我们向 foo(..) 传入实参 6 和 7 分别作为参数 x 和 y。foo(..) 向调用代码返回 42。
- 现在我们可以看到生成器和普通函数在调用上的一个区别。显然 foo(6,7) 看起来很熟悉。但难以理解的是,生成器 *foo(..) 并没有像普通函数一样实际运行。
- 事实上,我们只是创建了一个迭代器对象,把它赋给了一个变量 it,用于控制生成器 foo(..)。然后调it.next(),指示生成器 foo(..) 从当前位置开始继续运行,停在下 一个 yield 处或者直到生成器结束。
- 这个 next(..) 调用的结果是一个对象,它有一个 value 属性,持有从 *foo(..) 返回的值 (如果有的话)。换句话说,yield 会导致生成器在执行过程中发送出一个值,这有点类似于中间的 return。
除了能够接受参数并提供返回值之外,生成器甚至提供了更强大更引入注目的内建消息输入输出能力,通过yield和next(…)实现。
function *foo(x){
let y = x * (yield);
return y;
}
const iterator = foo(6);
// 启动foo
iterator.next();
const res = iterator.next(7);
console.log(res.value); // 42
首先,传入 6 作为参数 x。然后调用 it.next(),这会启动 foo(..)。
在 foo(..) 内部,开始执行语句 var y = x ..,但随后就遇到了一个 yield 表达式。它就会在这一点上暂停foo(..),并在本质上要求调用代码为 yield 表达式提供一个结果值。接下来,调用 it.next( 7 ),这一句把值 7 传回作为被暂停的 yield 表达式的结果。
所以,这时赋值语句实际上是let y = 6 7。现在,return y 返回值42作为调用iterator.next(7)的结果。
第一个 next(..) 总是启动一个生成器,并运行到第一个 yield 处。不过,是第二个next(..) 调用完成第一个被暂停的 yield 表达式,第三个 next(..) 调用完成第二个 yield,以此类推。
第一个 yield 基本上是提出了一个问题:“这里我应该插入什么值?”第一个 next() 已经运行,使得生成器启动并运行到此处,所以显 然它无法回答这个问题。因此必须由第二个 next(..) 调用回答第一个 yield 提出的这个问题。
消息是双向传递的——yield.. 作为一个表达式可以发出消息响应 next(..) 调用,next(..) 也可以向暂停的 yield 表达式发送值。
考虑下面这段稍稍调整过的代码:
function *foo(x){
let y = x * (yield "hello");
return y;
}
const iterator = foo(6);
let res = iterator.next();
console.log(res.value);
iterator.next(7); // 向等待的yield传入7
console.log(res.value); // 42
生成器产生值
通过生成器实现无限数字序列生产者
function *something(){
let nextVal;
while(true){
if(nextVal == undefined){
nextVal = 1;
}else{
nextVal = (3 * nextVal) + 6;
}
yield nextVal;
}
}
for(let v of something()){
console.log(v);
if(v > 500){
break;
}
}
生成器会在每个 yield 处暂停,函数 *something() 的 状态(作用域)会被保持,即意味着不需要闭包在调用之间保持变量状态。
something() 调用产生一个迭代器,但 for..of 循环需要的是一个 iterable,生成器的迭代器也有一个Symbol.iterator 函数,基本上这个函数做的就是 return this。换句话说,生成器的迭代器也是一个iterable。
似乎 *something() 生成器的迭代器实例在循环中的 break 调用之后就永远留在了挂起状态。
其实有一个隐藏的特性会帮助你管理此事。for..of 循环的“异常结束”(也就是“提前终止”),通常由 break、return 或者未捕获异常引起,会向生成器的迭代器发送一个信号使其终止。
生成器 + Promise
Promise加生成器可以将异步代码变成”看似同步的异步代码”,yield后面紧接着一个Promise,然后通过这个Promise来控制生成器的迭代器。
function promise_test(){
new Promise((resolve,reject) => {
setTimeout(() => {
resolve(100);
},1000);
}).then((val) => {
iterator.next(val);
});
}
function *test(){
let text = yield promise_test();
console.log(val);
console.log('这是测试');
}
let iterator = test();
iterator.next();
promise_test()没有返回值,并且迭代器控制代码并不关心yield出来的值。当生成器启动后,会在let text = yield promise_test();代码段暂停,然后当promise决议之后,我们又重新启动生成器,使得异步代码看起来变成了同步。