第7章 迭代器和生成器
第7章 迭代器和生成器
术语“迭代”源自拉丁语itero,表示“重复”或“再次执行”。在软件的环境中,“迭代”是指按顺序重复执行一个程序多次,并且通常期望终止。 ES6规范引入了两个高级功能(迭代器和生成器),以实现更简洁,更快和更容易的迭代。
迭代器简介
在JavaScript种,最简单的迭代之一是循环计数:
for (let i = 1; i <= 10; ++i) {
console.log(i);
}
循环是一种基本的迭代工具,因为循环可以指定应该进行多少次迭代以及在每次迭代中应该进行什么迭代。每个循环迭代将在另一个循环开始之前完成执行,并且每次迭代发生的顺序都已明确定义。
迭代可发生在有序集合上。(“有序”是指存在一个公认的顺序,所有项都可遍历,并带有确定的开始和结束项。)在JavaScript中,最常见的有序集合是数组:
let collection = ['foo', 'bar', 'baz'];
for (let index = 0; index < collection.length; ++index) {
console.log(collection[index]);
}
因为数组的长度已知,并且可以通过其索引获取数组中的每个项,所以可以通过增加可能的索引范围来按顺序遍历整个数组。
这种循环并不理想,因为:
遍历数据结构需要有关如何使用数据结构的特定知识,数组中的每一项只能通过数组对象的索引获取,
其它数据结构可能不行。
遍历的顺序不是数据结构所固有的。可递增索引访问数组,但其他数据结构可能不行。
ES5引入了Array.prototype.forEach()方法,很接近需求但还是不理想:
let collection = ['foo', 'bar', 'baz'];
collection.forEach((item) => console.log(item));
// foo
// bar
// baz
这解决了数组对象中单独跟踪索引并获取数组项的问题。但是,无法终止此迭代。该方法仅限于数组,并且回调结构较笨拙。
迭代器模式
迭代器模式(iterator pattern)描述了一种解决方案,某些事物可以描述为“可迭代”的,可以实现正式的迭代接口并由Iterator使用。
“可迭代”的概念有点抽象,通常,象数组或集合是可迭代集合对象,都有有限的可计数的元素和清晰的迭代顺序:
// 数组有有限的元素,按索引递增的遍历每个值
let arr = [3, 1, 4];
// Set有有限的元素,按插入顺序遍历每个值
let set = new Set().add(3).add(1).add(4);
但是,迭代器不必只链接到集合对象。它也可以链接到表现的像数组的对象,例如本章前面的计数循环。在此循环中生成的值是短暂的,也正是这样的循环在执行迭代。此计数循环和数组都可以表现为可迭代的。
任何实现迭代接口的东西都可以由实现Iterator接口的对象“消耗”。iterator是按需创建的单独对象,仅能使用一次。每个迭代器都与一个可迭代对象相关联,并且迭代器公开一个API以迭代与之关联的可迭代对象一次。迭代器不需要了解与之关联的可迭代对象的结构;它只需知道如何获取连续的值。
可迭代对象协议
实现迭代接口既需要自证支持迭代的能力,又需要创建实现Iterator接口的对象的能力。在ECMAScript中,这意味着它必须公开一个属性,即“默认迭代器”,并使用特殊的Symbol .iterator为键。这个默认的迭代器属性必须引用一个迭代器工厂函数, 该函数调用时将产生一个新的迭代器 。
许多内建类型实现了迭代接口:
String
Array
Map
Set
arguments对象
一些DOM集合类型如NodeList
检查默认迭代器属性会暴露工厂函数:
let num = 1;
let obj = {};
// 这两个没有迭代器工厂
console.log(num[Symbol.iterator]); // undefined
console.log(obj[Symbol.iterator]); // undefined
let str = 'abc';
let arr = ['a', 'b', 'c'];
let map = new Map().set('a', 1).set('b', 2).set('c', 3);
let set = new Set().add('a').add('b').add('c');
let els = document.querySelectorAll('div');
// 这些类型都有迭代器工厂
console.log(str[Symbol.iterator]); // ƒ [Symbol.iterator]() { [native code] }
console.log(arr[Symbol.iterator]); // ƒ values() { [native code] }
console.log(map[Symbol.iterator]); // ƒ entries() { [native code] }
console.log(set[Symbol.iterator]); // f values() { [native code] }
console.log(els[Symbol.iterator]); // f values() { [native code] }
// 调用工厂函数将产生一个迭代器
console.log(str[Symbol.iterator]()); // StringIterator {}
console.log(arr[Symbol.iterator]()); // ArrayIterator {}
console.log(map[Symbol.iterator]()); // MapIterator {"a" => 1, "b" => 2, "c" => 3}
console.log(set[Symbol.iterator]()); // SetIterator {"a", "b", "c"}
console.log(els[Symbol.iterator]()); // Array Iterator {}
不一定需要显示调用这些工厂函数来生成迭代器,任何实现迭代协议的以下结构将自动调用:
for...of循环
数组解构
延展操作符
Array.from()
Set创建
Map创建
Promise.all(),其期望一个可迭代promise
Promise.race(),也期望一个可迭代promise
yield*操作符,生成器中使用
这些原生语言结构在幕后调用可迭代对象提供的工厂函数来创建迭代器:
let arr = ['foo', 'bar', 'baz'];
// for...of循环
for (let el of arr) {
console.log(el);
}
// foo
// bar
// baz
// 数组解构
let [a, b, c] = arr;
console.log(a, b, c); // foo, bar, baz
// 延展操作符
let arr2 = [...arr];
console.log(arr2); // ['foo', 'bar', 'baz']
// Array.from()
let arr3 = Array.from(arr);
console.log(arr3); // ['foo', 'bar', 'baz']
// Set构造函数
let set = new Set(arr);
console.log(set); // Set(3) {'foo', 'bar', 'baz'}
// Map构造函数
let pairs = arr.map((x, i) => [x, i]);
console.log(pairs); // [['foo', 0], ['bar', 1], ['baz', 2]]
let map = new Map(pairs);
console.log(map); // Map(3) { 'foo'=>0, 'bar'=>1, 'baz'=>2 }
如果父类原型链实现了迭代接口,则子类亦是:
class childArr extends Array{}
let carr = new childArr(0,1,2,3,4,5);
console.log(...carr);//0 1 2 3 4 5
迭代器协议
迭代器是一个一次性对象,它将迭代与之关联的所有可迭代对象。 Iterator API使用next()方法进行迭代。每次连续调用next()时,它将返回一个IteratorResult对象。
next()方法返回的对象有两个属性:done,布尔值,指示是否可以再次调用next()以获取更多值;以及value,它将包含可迭代对象的下一个值或undefined(done为true)。done:true术语称为“exhaustion”。实例如下:
// 可迭代对象
let arr = ['Ciri','Geralt','Triss','Yennefer'];
let iter = arr[Symbol.iterator]();
console.log(iter.next());//{value: "Ciri", done: false}
console.log(iter.next());//{value: "Geralt", done: false}
arr.splice(0,0,'Vesemir');//Geralt被重复迭代
//迭代器维持对可迭代对象的引用,当引用对象改变时,迭代也会发生相应变化
console.log(iter.next());//{value: "Geralt", done: false}
arr.splice(3,0,'Zoltan');//正常
console.log(iter.next());//{value: "Zoltan", done: false}
console.log(iter.next());//{value: "Triss", done: false}
console.log(iter.next());//{value: "Yennefer", done: false}
console.log(iter.next());//{value: undefined, done: true}
console.log(iter.next());//{value: undefined, done: true}
一旦迭代器到达状态done:true时,再调用next()效果都一样。
每个迭代器代表该迭代器的一次有序遍历。不同的实例将独立遍历可迭代对象:
let arr = ['foo', 'bar'];
let iter1 = arr[Symbol.iterator]();
let iter2 = arr[Symbol.iterator]();
console.log(iter1.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'foo' }
console.log(iter2.next()); // { done: false, value: 'bar' }
console.log(iter1.next()); // { done: false, value: 'bar' }
迭代器不绑定到可迭代对象的快照。它仅使用游标跟踪可迭代对象的进度。如果迭代过程中可迭代对象发生改变,则迭代器将合并更改:
let arr = ['foo', 'baz'];
let iter = arr[Symbol.iterator]();
console.log(iter.next()); // { done: false, value: 'foo' }
// 在数组中间插入值
arr.splice(1, 0, 'bar');
console.log(iter.next()); // { done: false, value: 'bar' }
console.log(iter.next()); // { done: false, value: 'baz' }
console.log(iter.next()); // { done: true, value: undefined }
注意:迭代器维持对可迭代对象的引用,因此迭代器的存在将阻止对可迭代对象进行垃圾回收。
类实现可迭代接口:
class Foo {
[Symbol.iterator]() {
return {
next() {
return {
done: false,
value: 'haha'
};
}
}
}
}
let f = new Foo();
console.log(f[Symbol.iterator]());//{next: ƒ}
自定义迭代器定义
实现Iterator接口的任何对象都可以用作迭代器。请看以下示例,其中定义了Counter类以迭代特定的次数:
class Counter{
constructor(limit) {
this.count = 1;
this.limit = limit;
}
next(){
if(this.count <= this.limit){
return {done:false,value:this.count++};
}else{
return {done:true,value:undefined};
}
}
[Symbol.iterator](){
console.log(this);//counter实例
return this;
}
}
let counter = new Counter(9);
console.log(...counter);//1 2 3 4 5 6 7 8 9
console.log(...counter);//每个实例只能迭代一次
为了允许从单个可迭代对象创建多个迭代器,必须在每个迭代器的基础上创建计数器。为了解决这个问题,可以返回一个迭代器对象,该对象具有可通过闭包获得的计数器变量:
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1;
let limit = this.limit;
return {
next() {
if (count <= limit) {
return {
done: false,
value: count++
};
} else {
return {
done: true,
value: undefined
};
}
}
};
}
}
let counter = new Counter(9);
//console.log(conter);
console.log(...counter); //1 2 3 4 5 6 7 8 9
console.log(...counter); //1 2 3 4 5 6 7 8 9
console.log(counter[Symbol.iterator]);
//ƒ [Symbol.iterator]() {native code}
以这种方式创建的迭代器还实现了迭代接口。 Symbol.iterator属性指代返回相同的迭代器的一个工厂函数:
let arr = [1, 2, 3, 4, 5];
let iter = arr[Symbol.iterator]();
console.log(iter[Symbol.iterator]);
//ƒ [Symbol.iterator]() { [native code] }
let iter2 = iter[Symbol.iterator]();
console.log(iter === iter2); //true
for (let e of iter2) {
console.log(e); //1,2,3,4,5
}
因为每个迭代器都实现了迭代接口,所以它们可在任何可迭代的地方使用,如for...of:
let arr = [3, 1, 4];
let iter = arr[Symbol.iterator]();
for (let item of arr) {
console.log(item);//3,1,4
}
for (let item of iter) {
console.log(item);//3,1,4
}
提前终止迭代器
可选的return()方法仅允许在迭代器提前关闭时执行。可能发生这种情况的场景包括:
for...of中通过break,continue,return或throw退出循环
解构操作未完成所有的值
return()方法必须返回一个有效的IteratorResult对象。一个简单的迭代器实现应该只返回{done:true},因为返回值仅在生成器环境中使用。
如下面的代码所示,内置语言构造一旦识别出不需要迭代其他值,就会自动调用return()方法:
class Counter {
constructor(limit) {
this.limit = limit;
}
[Symbol.iterator]() {
let count = 1;
let limit = this.limit;
return {
next() {
if (count <= limit) {
return {
done: false,
value: count++
};
} else {
return {
done: true,
value: undefined
};
}
},
return(){
console.log('提前终止');
return {done:true};
}
};
}
}
let counter = new Counter(9);
for(const e of counter){
console.log(e);
if(e >=3){
break;
}
}//1,2,3,提前终止
console.log(...counter); //1 2 3 4 5 6 7 8 9
let counter2 = new Counter(5);
let [a, b] = counter2;
// 提前终止
如果迭代器未关闭(带return()函数可关闭),则可继续,如数组迭代器是不可关闭的:
let a = [1,2,3,4,5,6];
let iter = a[Symbol.iterator]();
for(let i of iter){
if(i < 3){
console.log(i);
}else{
break;
}//1,2
}
console.log(...iter);//4,5,6
因为return()方法是可选的,所以并非所有迭代器都是可关闭的。可以通过测试迭代器实例上的return属性是否为函数对象来确定迭代器是否可关闭。但是,仅将方法添加到不可关闭的迭代器将不会使其变为可关闭,因为调用return()不会强制迭代器进入关闭状态。但是,return()方法仍将被调用:
let a = [1, 2, 3, 4, 5];
let iter = a[Symbol.iterator]();
iter.return = function() {
console.log('提前终止');
return {
done: true
};
};
for (let i of iter) {
console.log(i);
if (i > 2) {
break
}
}
// 1
// 2
// 3
// 提前终止
for (let i of iter) {
console.log(i);
}
// 4
// 5
生成器
生成器是ES6规范中引入的一种令人愉悦的灵活结构,它具有在单个函数块内暂停和恢复代码执行的功能。这种新能力的含义是深远的。除其他外,它还允许定义自定义迭代器和实现协程的能力。
生成器基础
生成器采用带有星号的函数的形式定义。在函数定义有效的地方,生成器函数定义也有效:
// 生成器函数声明
function* generatorFn() {}
// 生成器函数表达式
let generatorFn = function*() {}
// 对象字面量形式的生成器函数
let foo = {
* generatorFn() {}
}
// 类实例方法形式的生成器函数
class Foo {
* generatorFn() {}
}
//类静态方法形式的生成器函数
class Bar {
static * generatorFn() {}
}
注意:箭头函数不能用作生成器函数。
生成器函数被调用时将生成一个generator对象,生成器对象开始处于挂起状态。像迭代器一样,这些生成器对象实现了Iterator接口,因此具有next()方法,该方法在被调用时指示生成器开始或恢复执行。
function *generatorFn(){};
const g = generatorFn();
console.log(g);//generatorFn {<suspended>}
console.log(g.next);//ƒ next() { [native code] }
生成器next()方法的返回值与迭代器next()方法的返回值匹配,都带有done和value属性。如果生成器函数体为空的话,调用next()方法将导致生成器到达done:true状态:
function* generatorFn() {}
let generatorObject = generatorFn();
console.log(generatorObject); // generatorFn {<suspended>}
console.log(generatorObject.next()); // {value: undefined, done: true}
value属性是生成器函数的返回值,默认值为undefined。可以通过生成器函数的return指定:
function *generatorFn(){return '666';};
const g = generatorFn();
console.log(g.next());//{value: "666", done: true}
生成器函数的执行仅在首次调用next()时执行:
function* generatorFn() {
console.log('Gwent!!!');
}
// 生成器函数开始调用时没有输出
let generatorObject = generatorFn();
generatorObject.next(); //Gwent!!!
generatorObject.next(); //不再执行
生成器对象实现了迭代接口,并且它们的默认迭代器是自引用的:
function* generatorFn() {}
console.log(generatorFn);
// f* generatorFn() {}
console.log(generatorFn()[Symbol.iterator]);
// f [Symbol.iterator]() {native code}
console.log(generatorFn());
// generatorFn {<suspended>}
console.log(generatorFn()[Symbol.iterator]());
// generatorFn {<suspended}
const g = generatorFn();
console.log(g === g[Symbol.iterator]());
// true
使用yield中断执行
yield关键字允许生成器停止和开始执行,这正是让生成器真正有用的原因。生成器函数将继续正常执行,直到遇到yield关键字。遇到关键字时,将暂停执行并保留函数的作用域状态。仅当在生成器对象上调用next()方法时,执行才会恢复:
function *generatorFn(){yield;};
console.log(generatorFn().next());//{value: undefined, done: false}
console.log(generatorFn().next());//{value: undefined, done: false}
let g = generatorFn();
console.log(g.next());//{value: undefined, done: false}
console.log(g.next());//{value: undefined, done: true}
yield关键字的行为像是函数中途返回,并且yield的值可在next()方法返回的对象内部可用。通过yield关键字退出的生成器函数的done值为false;通过return关键字退出的生成器函数的done值为true:
function *generatorFn(){
yield 'Ciri';
yield 'Geralt';
return 'Triss';
}
console.log(generatorFn().next());//value: "Ciri", done: false}
console.log(generatorFn().next());//value: "Ciri", done: false}
let g = generatorFn();
console.log(g.next());//{value: "Ciri", done: false}
console.log(g.next());//{value: "Geralt", done: false}
console.log(g.next());//{value: "Triss", done: true}
console.log(g.next());//{value: undefined, done: true}
生成器函数内的执行进度将限制在每个生成器对象实例的范围内。在一个生成器对象上调用next()不会影响任何其他对象:
function* generatorFn() {
yield 'Ciri';
yield 'Geralt';
return 'Triss';
}
let generatorObject1 = generatorFn();
let generatorObject2 = generatorFn();
console.log(generatorObject1.next()); //{value: "Ciri", done: false}
console.log(generatorObject2.next()); //{value: "Ciri", done: false}
console.log(generatorObject2.next()); //{value: "Geralt", done: false}
console.log(generatorObject1.next()); //{value: "Geralt", done: false}
yield关键字只能在生成器函数内部使用;其他任何地方都会引发错误。与函数return关键字一样,yield关键字必须立即出现在生成器函数定义中。在非生成器函数中进一步嵌套将引发语法错误:
// valid
function* validGeneratorFn() {
yield;
}
// invalid
function* invalidGeneratorFnA() {
function a() {
yield;
}
}
// invalid
function* invalidGeneratorFnB() {
const b = () => {
yield;
}
}
// invalid
function* invalidGeneratorFnC() {
(() => {
yield;
})();
}
使用生成器对象作为可迭代对象
很少需要在生成器对象上显示调用next()方法,以可迭代对象的方式使用生成器会更加有用:
function* generatorFn() {
yield 1;
yield 2;
yield 3;
}
for (const x of generatorFn()) {
console.log(x); //1,2,3
}
function *nTimes(n){
while(n--){
yield n;
}
}
let n = nTimes(9);
console.log(...n);//8 7 6 5 4 3 2 1 0
使用yield输入输出
yield关键字也可作为函数过渡形参使用。生成器上次暂停执行的位置处的yield关键字将夺取传递给next()的第一个值。
令人困惑的是,提供给第一个next()调用的值并没有使用,因为此next()用于开始生成器函数的执行:
function* generatorFn(initial) {
console.log(initial);
console.log(yield);
console.log(yield);
}
let generatorObject = generatorFn('foo');
generatorObject.next('bar'); // foo
generatorObject.next('baz'); // baz
generatorObject.next('qux'); // qux
yield关键字可以同时用作输入和输出:
function* generatorFn() {
return yield 'foo';
}
let generatorObject = generatorFn();
console.log(generatorObject.next()); // {value: "foo", done: false}
console.log(generatorObject.next('bar')); // {value: "bar", done: true}
因为该函数必须计算整个表达式以确定要返回的值,所以在遇到yield关键字时它将暂停执行,并计算yield的值为foo。
后续的next()调用将提供值bar作为该相同yield的值,然后将其计算为生成器函数的返回值。
yield关键字并不是只能使用一次:
function* generatorFn() {
for (let i = 0;; ++i) {
yield i;
}
}
let generatorObject = generatorFn();
console.log(generatorObject.next().value); // 0
console.log(generatorObject.next().value); // 1
console.log(generatorObject.next().value); // 2
console.log(generatorObject.next().value); // 3
console.log(generatorObject.next().value); // 4
console.log(generatorObject.next().value); // 5
yield一个可迭代对象
可以增强yield的行为,以使其遍历一个可迭代对象,并将其内容一次性生成。这可以通过给对象前置星号完成:
function* generatorFn() {
yield*[1, 2, 3];
}
for (const x of generatorFn()) {
console.log(x);//1,2,3
}
效果同:
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;//1,2,3
}
}
星号位置并不影响输出:
function* generatorFn() {
yield* [1, 2];
yield *[3, 4];
yield * [5, 6];
}
console.log(...generatorFn()); //1,2,3,4,5,6
yield*的值是关联迭代器的done值为true时value的值,对于普通迭代器,此值是undefined:
function* generatorFn() {
console.log('iter value:', yield*[1, 2, 3]);
}
let go = generatorFn();
console.log(go.next()); //{value: 1, done: false}
console.log(go.next()); //{value: 2, done: false}
console.log(go.next()); //{value: 3, done: false}
console.log(go.next()); //{value: undefined, done: true}
//iter value: undefined
对于生成器函数产生的迭代器,此值是生成器函数的返回值(表达式为其计算结果值):
function* innerGeneratorFn() {
yield 'foo';
return 'bar';
}
function* outerGeneratorFn(genObj) {
console.log('iter value:', yield* innerGeneratorFn());
}
for (const x of outerGeneratorFn()) {
console.log('value:', x);
}
// value: foo
// iter value: bar
使用yield*的递归算法
当在递归运算中使用时,yield*最有用,在递归运算中,生成器可以yield自身。
function* nTimes(n) {
if (n > 0) {
yield* nTimes(n - 1);
yield n - 1;
}
}
console.log(...nTimes(6));//0 1 2 3 4 5
在此例中,每个生成器首先yield新创建的生成器对象的每个值,然后yield单个整数。这样的结果是生成器函数递归地递减计数器值并实例化另一个生成器对象,在顶层的生成器对象将创建单个可迭代对象并返回递增的整数。
图实现,生成随机双向图:
class Node {
constructor(id) {
this.id = id;
this.neighbors = new Set();
}
connect(node) {
if (node !== this) {
this.neighbors.add(node);
node.neighbors.add(this);
}
}
class RandomGraph {
constructor(size) {
this.nodes = new Set();
// 创建节点
for (let i = 0; i < size; ++i) {
this.nodes.add(new Node(i));
}
// 随机连接节点
const threshold = 1 / size;
for (const x of this.nodes) {
for (const y of this.nodes) {
if (Math.random() < threshold) {
x.connect(y);
}
}
}
}
// 此处用于debug
print() {
for (const node of this.nodes) {
const ids = [...node.neighbors]
.map((n) => n.id)
.join(',');
console.log(`${node.id}: ${ids}`);
}
}
}
const g = new RandomGraph(6);
g.print();//随机不贴了
图非常适合于递归遍历,使用递归生成器可以做到这一点。为此,生成器函数必须接受一个可迭代对象,yield该可迭代对象中的每个值,然后递归每个值。可以通过从一个节点开始并尝试访问每个节点来完成此测试。结果是深度优先搜索的非常简洁的实现:
class Node {
constructor(id) {
this.id = id;
this.neighbors = new Set();
}
connect(node) {
if (node !== this) {
this.neighbors.add(node);
node.neighbors.add(this);
}
}
}
class RandomGraph {
constructor(size) {
this.nodes = new Set();
// 创建节点
for (let i = 0; i < size; ++i) {
this.nodes.add(new Node(i));
}
// 随机连接节点
const threshold = 1 / size;
for (const x of this.nodes) {
for (const y of this.nodes) {
if (Math.random() < threshold) {
x.connect(y);
}
}
}
}
print() {
for (const node of this.nodes) {
const ids = [...node.neighbors]
.map((n) => n.id)
.join(',');
console.log(`${node.id}: ${ids}`);
}
}
isConnected() {
const visitedNodes = new Set();
}
}
function* traverse(nodes) {
for (const node of nodes) {
if (!visitedNodes.has(node)) {
yield node;
yield* traverse(node.neighbors);
}
}
}
// 抓取Set中第一个节点
const firstNode = this.nodes[Symbol.iterator]().next().value;
// 使用递归生成器迭代每个节点
for (const node of traverse([firstNode])) {
visitedNodes.add(node);
}
return visitedNodes.size === this.nodes.size;
const g = new RandomGraph(6);
g.print();//随机不贴
使用生成器作为默认迭代器
由于生成器对象实现了迭代接口,并且生成器函数和默认迭代器均可被调用以生成迭代器,因此生成器非常适合用作默认迭代器。以下是一个简单的示例,其中默认迭代器可以在一行中yield类的内容:
class Foo {
constructor() {
this.values = [1, 2, 3];
}
*[Symbol.iterator]() {
yield* this.values;
}
}
const f = new Foo();
for (const x of f) {
console.log(x);
}
// 1
// 2
// 3
在这里,for ... of循环调用默认的迭代器(恰好是生成器函数)并生成生成器对象。生成器对象是可迭代的,因此可以在迭代中使用。
提前终止生成器
像迭代器一样,生成器也支持“可关闭”的概念。对于要实现Iterator接口的对象,它必须具有next()以及(可选)return()方法,以用提前终止迭代器。生成器对象同时具有这两种方法以及附加的第三个方法throw():
function* generatorFn() {}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.next); // f next() { [native code] }
console.log(g.return); // f return() { [native code] }
console.log(g.throw); // f throw() { [native code] }
return()和throw()方法是可用于强制将生成器变为为关闭状态的两个方法。
return()方法
return()方法将强制生成器进入关闭状态,并且提供给return()的值将是最终的迭代器对象中的值:
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
console.log(g.return(4)); //{value: 4, done: true}
console.log(g); // generatorFn {<closed>}
与迭代器不同,所有生成器对象都有一个return()方法,该方法强制进入关闭状态,一旦到达就无法退出。随后调用next()会一直是done:true状态, 但是任何提供的返回值都不会存储或传播:
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g.next()); //
{value: 1, done: false}
console.log(g.return(4)); // {value: 4, done: true}
console.log(g.next()); // {value: undefined, done: true}
console.log(g.next()); // {value: undefined, done: true}
console.log(g.next()); // {value: undefined, done: true}
内置的结构(例如for...of循环)将明智地忽略done:true的IteratorObject:
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
for (const x of g) {
if (x > 1) {
g.return(4);
}
console.log(x);
}
// 1
// 2
throw()方法
throw()方法将在生成器对象挂起时将所提供的错误注入生成器对象。如果未解决错误,则生成器将关闭:
function* generatorFn() {
for (const x of [1, 2, 3]) {
yield x;
}
}
const g = generatorFn();
console.log(g); // generatorFn {<suspended>}
try {
g.throw('foo');
} catch (e) {
console.log(e); // foo
}
console.log(g); // generatorFn {<closed>}
但是,如果错误是在生成器函数内部处理的,则错误不会关闭并且可以恢复执行。错误处理将跳过该yield,因此在本示例中,将看到它跳过了一个值。如下所示:
function* generatorFn() {
for (const x of [1, 2, 3]) {
try {
yield x;
} catch (e) {}
}
}
const g = generatorFn();
console.log(g.next()); // {value: 1, done: false}
g.throw('foo');
console.log(g.next()); // {value: 3, done: false}
在此示例中,生成器在try / catch块内的yield关键字处暂停执行。挂起时,throw()注入foo错误,该错误由yield关键字引发。由于此错误是在生成器的try / catch块中引发的,因此随后在生成器内部被捕获。但是,由于yield引发了该错误,因此生成器将不会生成值 2 。相反,生成器函数继续执行,继续进行下一个循环迭代,在该循环中再次遇到yield关键字,这次生成值 3 。