ECMAScript6 - Iterators and Generators

Iterators and Generators

Iterators have been used in many programming languages as a way to more easily work with collections of data. In ECMAScript 6, JavaScript adds iterators as an important feature of the language. When coupled with new array methods and new types of collections (such as sets and maps), iterators become even more important for efficient processing of data.

  在其他的语言中,遍历器被用来更加方便的处理数据集合。在ES6中,js增加了遍历器作为这个语言的重要特征。当结合新的数组方法和新的集合类型(例如set和map),因为高效的处理数据的能力,遍历器变得更加重要。

What are Iterators?-什么是遍历器

Iterators are nothing more than objects with a certain interface. That interface consists of a method called next() that returns a result object. The result object has two properties, value, which is the next value, and done, which is a boolean value that’s true when there are no more values to return. The iterator keeps an internal pointer to a location within a collection of values and, with each call to next(), returns the next appropriate value.

  遍历器只是一个拥有特定接口的对象。这个接口包含了一个方法叫做next()可以返回结果对象。结果对象有两个属性:value-next的值,和done-布尔值,为true的时候表示下一次将没有返回值。遍历器有一个内部指针来定位集合的值,每次调用next方法,都返回下一个适当的值。

If you call next() after the last value has been returned, the method returns done as true and value contains the return value for the iterator. The return value is not considered part of the data set, but rather a final piece of related data or undefined if no such data exists. (This concept will become clearer in the generators section later in this chapter.)

  如果在返回最后一个值之后又调用了next(),这个方法返回的done的值为true,value包含了遍历器的返回值,返回值不是集合的一部分,而是最后一个相关的数据或者undefined如果没定义这个数据。

With that understanding, it’s fairly easy to create an iterator using ECMAScript 5, for example:

  理解到这,使用ES5创建一个遍历器也很简单,例如:

function createIterator(items) {

    var i = 0;

    return {
        next: function() {

            var done = (i >= items.length);
            var value = !done ? items[i++] : undefined;

            return {
                done: done,
                value: value
            };

        }
    };
}

var iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

 The createIterator() function in this example returns an object with a next() method. Each time the method is called, the next value in the items array is returned as value. When i is 4, items[i++] returns undefined and done is true, which fulfills the special last case for iterators in ECMAScript 6.

  本例中的createIterator函数返回了一个拥有next方法的对象。每当这个方法调用的时候,items数组中的下一个值作为value属性返回。当i等于4的时候,item[i++]返回undefined和done的值为true,符合ES6中的遍历器特性。

ECMAScript 6 makes use of iterators in a number of places to make dealing with collections of data easier, so having a good basic understanding allows you to better understand the language as a whole.

  ES6中在很多地方使用遍历器来处理集合数据更加方便,所以拥有良好的理解可以让你更好的理解整个语言。

Generators-生成器

You might be thinking that iterators sound interesting but they look like a bunch of work. Indeed, writing iterators so that they adhere to the correct behavior is a bit difficult, which is why ECMAScript 6 provides generators. A generator is a special kind of function that returns an iterator. Generator functions are indicated by inserting a star character (*) after the function keyword (it doesn’t matter if the star is directly next to function or if there’s some whitespace between them). The yield keyword is used inside of generators to specify the values that the iterator should return when next() is called. So if you want to return three different values for each successive call to next(), you can do so as follows:

  你可能在想,遍历器听起来很有意思,但是它们需要很多的工作。确实,编写一个确保行为正确的遍历器有点困难,这就是为什么ES6引入了generators(生成器),一个generators是一个能够返回遍历器的特殊函数。生成器在function关键字之后增加一个星字符(*)来表示(星字符与function关键字之间是否又空格没有影响)。yield关键字用在生成器内部来制定当遍历器的next方法调用的时候应该返回什么值。所以如果你想每次调用next的时候返回三个不同的值,你可以像下面这样做:

// generator
function *createIterator() {
    yield 1;
    yield 2;
    yield 3;
}

// generators are called like regular functions but return an iterator
let iterator = createIterator();

for (let i of iterator) {
    console.log(i);
}

 This code outputs the following:

这段代码的输出如下:

1
2
3

 In this example, the createIterator() function is a generator (as indicated by the * before the name) and it’s called like any other function. The value returned is an object that adheres to the iterator pattern. Multiple yield statements inside the generator indicate the progression of values that should be returned when next() is called on the iterator. First, next() should return 1, then 2, and then 3 before the iterator is finished.

  在这个例子中,createIterator函数是一个生成器(函数名字之前有星字符),它的调用和其他函数一样。返回的值是一个遍历器形式的对象。生成器内部的多个yield声明表示当调用遍历器的next方法的时候这些值会依次被返回。第一次next返回1,然后是2和3.

Perhaps the most interesting aspect of generator functions is that they stop execution after each yield statement, so yield 1 executes and then the function doesn’t execute anything else until the iterator’s next() method is called. At that point, execution resumes with the next statement after yield 1, which in this case is yield 2. This ability to stop execution in the middle of a function is extremely powerful and lends to some interesting uses of generator functions (discussed later in this chapter).

  也许生成器最有趣的一方面在于每次yield声明执行之后,生成器函数就会停止执行,所以yield 1 执行了知道遍历器的next方法被调用之前这个函数都不会继续往下执行。对于这一点,在yeild声明之后恢复执行,本例中也就是yield2.在函数内部停止执行的能力很强大,这导致了生成器函数的很多有意思的用途。(稍后讨论)

 The yield keyword can be used with any value or expression, so you can do interesting things like use yield inside of a for loop:

  yield关键字可以和任何的值和表达式一起使用,所以在for循环中你可以使用yield来做一个有趣的事:

function *createIterator(items) {
    for (let i=0; i < items.length; i++) {
        yield items[i];
    }
}

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

In this example, an array is used in a for loop, yielding each item as the loop progresses. Each time yield is encountered, the loop stops, and each time next() is called on iterator, the loop picks back up where it left off.

  在这个例子中,循环一个数组,每次循环都输出数组的一项。每次遇到yield,循环停止,每次调用遍历器的next方法,循环都会从之前停止的地方开始。

Generator functions are an important part of ECMAScript 6, and since they are just functions, they can be used in all the same places.

  生成器是ES6 重要的一部分,因为他们只是函数,所以他们和函数的用法一样。

Generator Function Expressions-生成器函数表达式

Generators can be created using function expressions in the same way as using function declarations by including a star (*) character between the function keyword and the opening paren, for example:

  生成器可以使用函数表达式来创建,与函数声明相同的是和function关键字和开始括号之前增加一个星(*)符号。

let createIterator = function *(items) {
    for (let i=0; i < items.length; i++) {
        yield items[i];
    }
};

let iterator = createIterator([1, 2, 3]);

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

// for all further calls
console.log(iterator.next());           // "{ value: undefined, done: true }"

 In this code, createIterator() is created by using a generator function expression. This behaves exactly the same as the example in the previous section.

  这个代码中,createIterator通过使用生成器函数表达式来创建。这个例子与之前例子中的表现一致。

Generator Object Methods-生成器对象方法

Because generators are just functions, they can be added to objects the same way as any other functions. For example, you can use an ECMAScript 5-style object literal with a function expression:

  因为生成器只是函数,与其他函数一样它们也可以赋给对象。例如,你可以使用ES5形式的对象字面量使用函数表达式:

var o = {

    createIterator: function *(items) {
        for (let i=0; i < items.length; i++) {
            yield items[i];
        }
    }
};

let iterator = o.createIterator([1, 2, 3]);

 You can also use the ECMAScript 6 method shorthand by prepending the method name with a star (*):

  你可以使用ES6 方法速写格式通过在方法名字之前增加一个星号

var o = {

    *createIterator(items) {
        for (let i=0; i < items.length; i++) {
            yield items[i];
        }
    }
};

let iterator = o.createIterator([1, 2, 3]);

 This example is functionally equivalent to the previous one, the only difference is the syntax used.

  这个例子与之前的例子在功能上相同,不同的只是语法的使用不同。

Generator Class Methods - 生成器类方法

Similar to objects, you can add generator methods directly to classes using almost the same syntax:

  与对象相似,你可以使用相同的语法为类增加生成器方法:

class MyClass {

    *createIterator(items) {
        for (let i=0; i < items.length; i++) {
            yield items[i];
        }
    }

}

let o = new MyClass();
let iterator = o.createIterator([1, 2, 3]);

 The syntax is very similar to using shorthand object literal methods, as the asterisk needs to come before the method name.

  这个语法和对象字面量方法速写的语法非常类似,星号需要出现在方法名字之前。

iterable and for-of-迭代器和for-of

Closely related to the concept of an iterator is an iterable. An iterable is an object that has a default iterator specified using the @@iterator symbol. More specifically, @@iterator contains a function that returns an iterator for the given object. All of the collection objects, including arrays, sets, and maps, as well as strings, are iterables and so have a default iterator specified. Iterables are designed to be used with a new addition to ECMAScript: the for-of loop.

  与遍历器紧密相关的就是迭代器。一个迭代器是一个拥有默认遍历器的对象,使用@@iterator标识。更特殊的,@@iterator包含了一个为所给定的对象返回一个迭代器的函数。所有的结合对象,包括数组、set、map和字符串,都可以迭代,所以含有指定的迭代器。迭代器被设计以ES新增加的for-of方式使用。

The for-of loop is similar to the other loops in ECMAScript except that it is designed to work with iterables. The loop itself calls next() behind the scenes and exits when the done property of the returned object is true. For example:

  for-of循环与ES中的其他循环类似,除了它被设计成和迭代器一起工作。循环本身在后台调用next(),当done属性为true的时候,循环退出。例如:

let values = [1, 2, 3];

for (let i of values) {
    console.log(i);
}

 This code outputs the following:

这段代码的输出如下:

1
2
3

 The for-of loop in this example is first calling the @@iterator method to retrieve an iterator, and then calling iterator.next() and assigning the variable i to the value returned on the value property. So i is first 1, then 2, and finally 3. When done is true, the loop exits, so i is never assigned the value of undefined.

  这个例子中的for-of循环首先调用了@@iterator方法来检索一个迭代器,接着调用了iterator.next(),然后把变量i赋给value属性返回value属性。所以依次输出1、2、3。当done为true的时候退出,所以i永远都不会被赋值为undefined。


The for-of statement will throw an error when used on, a non-iterable, null, or undefined.

  当for-of用于一个不可迭代的类型或者null、undefined的时候,回抛出错误。


Accessing the Default Iterator-访问默认的迭代器

You can access the default iterator for an object using Symbol.iterator, for example:

  你可以通过对象的Symbol.iterator来访问默认的迭代器,例如:

let values = [1, 2, 3];
let iterator = values[Symbol.iterator]();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }" 

This code gets the default iterator for values and uses that to iterate over the values in the array. Knowing that Symbol.iterator specifies the default iterator, it’s possible to detect if an object is iterable by using the following:

  这段代码获取了values的默认迭代器,使用这个迭代器来遍历数组中的值, 知道Symbol.iterator是默认的迭代器,就能确定一个对象是否可以迭代。例如:
function isIterable(object) {
    return typeof object[Symbol.iterator] === "function";
}

console.log(isIterable([1, 2, 3]));     // true
console.log(isIterable("Hello"));       // true
console.log(isIterable(new Map()));     // true
console.log(isIterable(new Set()));     // true

The isIterable() function simply checks to see if a default iterator exists on the object and is a function. This is similar to the check that the for-of loop does before executing.

  isIterable()函数用来检测对象上是否存在一个默认的迭代器并且这个迭代器是一个函数。这个类似于for-of循环执行之前的检查。

Creating Iterables-创建迭代器

Developer-defined objects are not iterable by default, but you can make them iterable by using the @@iterator symbol. For example:

  开发者通过默认属性来明确一个对象不可迭代,但是你可以通过使用@@iterator标识来让对象变得可迭代,例如:

let collection = {
    items: [],
    *[Symbol.iterator]() {
        yield *this.items.values();
    }

};

collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let x of collection) {
    console.log(x);
}

// Output:
// 1
// 2
// 3

 This code defines a default iterator for a variable called collection using object literal method shorthand and a computed property using Symbol.iterator. The generator then delegates to the values() iterator of this.items. The for-of loop then uses the generator to create an iterator and execute the loop.

  这个代码通过使用对象字面量方法速写形式为变量定义了一个默认的迭代器叫做collection并且使用Symbol.iterator计算属性。生成器接着将this.items委托给values()迭代器。for-of循环使用这个生成器来创建迭代器和执行循环。

You can also define a default iterator using classes, such as:

  你也可以使用类来定义默认的迭代器,例如:

class Collection {

    constructor() {
        this.items = [];
    }

    *[Symbol.iterator]() {
        yield *this.items.values();
    }
}

var collection = new Collection();
collection.items.push(1);
collection.items.push(2);
collection.items.push(3);

for (let x of collection) {
    console.log(x);
}

// Output:
// 1
// 2
// 3

 This example mirrors the previous one with the exception that a class is used instead of an object literal.

  这个例子复现了前一个例子,不同之处在于这个一个类而不是对象字面量。

Default iterators can be added to any object by assigning a generator to Symbol.iterator. It doesn’t matter if the property is an own or prototype property, as for-of normal prototype chain lookup applies.

  默认的迭代器可以通过赋给一个生成器Symbol.iterator增加到对象上。这个属性是原有的还是原型上的并不重要,for-of原型链会寻找这个请求。

Built-in Iterators-内置的迭代器

Another way that ECMAScript 6 makes using iterators easier is by making iterators available on many objects by default. You don’t actually need to create your own iterators for many of the built-in types because the language has them already. You only need to create iterators when you find that the built-in ones don’t serve your purpose.

  ES6中的另一种方式是的迭代器更加容易-通过在许多对象上增加默认的迭代器。你不需要创建自己的迭代器,因为ES已经为你创建了。只有默认的迭代器不符合你的需求的时候才需要创建。

Collection Iterators-集合迭代器

The ECMAScript 6 collection objects, arrays, maps, and sets, all have three default iterators to help you navigate data. You can retrieve an iterator for a collection by calling one of these methods:

  ES6的集合对象,数组,map和set都有三个默认的迭代器来帮助你找到数据。你可以通过调用以下方法为集合检索迭代器。

  • entries() - returns an iterator whose values are a key-value pair.
  • entries-返回的迭代器的value值式键-值对
  • values() - returns an iterator whose values are the values of the collection.
  • values-返回的迭代器的value式集合的值
  • keys() - returns an iterator whose values are the keys contained in the collection.
  • keys-返回的迭代器的values是集合的键名。

The entries() iterator actually returns a two-item array where the first item is the key and the second item is the value. For arrays, the first item is the numeric index; for sets, the first item is also the value (since values double as keys in sets). Here are some examples:

  entries()迭代器实际上返回一个两个元素的数组,数组的第一个元素是键名,数组的第二个元素是键值。对于数组来说,第一个元素是数字的下标索引,对于sets,第一个元素也是键值。下面是例子:

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let entry of colors.entries()) {
    console.log(entry);
}

for (let entry of tracking.entries()) {
    console.log(entry);
}

for (let entry of data.entries()) {
    console.log(entry);
}

 This example outputs the following:

  这个例子的结果如下:

[0, "red"]
[1, "green"]
[2, "blue"]
[1234, 1234]
[5678, 5678]
[9012, 9012]
["title", "Understanding ECMAScript 6"]
["format", "ebook"]

 The values() iterator simply returns the values as they are stored in the collection. For example:

  values()迭代器只是返回集合中存储的值,例如:

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let value of colors.values()) {
    console.log(value);
}

for (let value of tracking.values()) {
    console.log(value);
}

for (let value of data.values()) {
    console.log(value);
}

 This example outputs the following:

这个例子的结果如下:

"red"
"green"
"blue"
1234
5678
9012
"Understanding ECMAScript 6"
"ebook"

 In this case, using values() returns the exact data contained in the value property returned from next().

  在这个例子中,使用values()返回了next()方法value属性的值。

The keys() iterator returns each key present in the collection. For arrays, this is the numeric keys only (it never returns other own properties of the array); for sets, the keys are the same as the values and so keys() and values() return the same iterator.

   keys()迭代起返回集合中的每一个键名。对于数组来说,仅仅返回数字的键名(永远不会返回数组本身的属性);对于set,键名和值相同,所以keys()和values()返回相同的迭代器。
let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

for (let key of colors.keys()) {
    console.log(key);
}

for (let key of tracking.keys()) {
    console.log(key);
}

for (let key of data.keys()) {
    console.log(key);
}

This example outputs the following:

这个例子的结果如下:

0
1
2
1234
5678
9012
"title"
"format"

 Additionally, each collection type has a default iterator that is used by for-of whenever an iterator isn’t explicitly specified. The default iterator for arrays and sets is values() while the default iterator for maps is entries(). This makes it a little bit easier to use collection objects in for-of:

  另外,每一种集合类型都有默认的迭代器用来for-of循环,无论迭代器时候明确的指定。数组和sets的默认的迭代器是values(),maps默认的迭代器是entries()。这对于集合使用for-of循环就更加方便了。

let colors = [ "red", "green", "blue" ];
let tracking = new Set([1234, 5678, 9012]);
let data = new Map();

data.set("title", "Understanding ECMAScript 6");
data.set("format", "ebook");

// same as using colors.values()
for (let value of colors) {
    console.log(value);
}

// same as using tracking.values()
for (let num of tracking) {
    console.log(num);
}

// same as using data.entries()
for (let entry of data) {
    console.log(entry);
}

 This example outputs the following:

  这个例子的结果如下:

"red"
"green"
"blue"
1234
5678
9012
["title", "Understanding ECMAScript 6"]
["format", "ebook"]

String Iterators-字符串迭代器

Beginning with ECMAScript 5, JavaScript strings have slowly been evolving to be more array-like. ECMAScript 5 formalizes bracket notation for access characters (text[0] to get the first character). Unfortunately, bracket notation works on code units rather than characters, so it cannot be used to access double-byte characters correctly. ECMAScript 6 has added a lot of functionality to fully support Unicode (see Chapter 1) and as such, the default iterator for strings works on characters rather than code units.

  在ES5之初,js的字符串已经逐渐向数组进化。ES5使方括号访问字符成为规范(text[0]获取第一个字符)。不幸的是,方括号对编码单元其作用而不是字符,所以不能用来获取双字节的字符。ES6增加了许多功能来充分的支持Unicode,所以字符串的迭代起可以对字符起作用而不是编码单元。

Using bracket notation and the length property, the code units are used instead of characters and the output is a bit unexpected:

  使用方括号和length属性,编码单元用来替代字符,输出的结果与想像的不太一致:

var message = "A ð ®· B";

for (let i=0; i < message.length; i++) {
    console.log(message[i]);
}

 This code outputs the following:

这段代码的结果如下:

A
(blank)
(blank)
(blank)
(blank)
B

Since the double-byte character is treated as two separate code units, there are four empty lines between A and B in the output.

 因为双字节的字符被当作是两个单独的编码单元,所以A和B 的输出结果之间有四个空行。

Using the default string iterator with a for-of loop results in a more appropriate result:

  在for-of循环中使用字符串的默认迭代器会得到更加合适的结果:

var message = "A ð ®· B";

for (let c of message) {
    console.log(c);
}

 This code outputs the following:

这段代码的结果如下:

A
(blank)
ð ®·
(blank)
B

 This output is more in line with what you might expect when working with characters. The default string iterator is ECMAScript 6’s attempt at solving the iteration problem by using characters instead of code units.

这个可能更符合你在处理字符时候的输出结果。字符串的默认迭代器是ES6试图解决使用字符代替编码单元迭代的解决方案。

NodeList Iterators-节点列表迭代器

In the Document Object Model (DOM), there is a NodeList type that represents a collection of elements in a document. For those who write JavaScript to run in web browsers, understanding the difference between NodeList objects and arrays has always been a bit difficult. Both use the length property to indicate the number of items and both use bracket notation to access individual items. However, internally a NodeList and an array behave quite differently, and so that has led to a lot of confusion.

  在文档对象模型(DOM)中,有一个NodeList类型代表文档中的元素集合。那些编写js代码运行在web浏览器中的程序员来说,理解NodeList对象和数组之间的区别有一点困难。他们都有length属性来表明元素的个数,也都可以使用方括号来获取每一个元素。然而,NodeList和数组在内部的行为非常不一样,所以会导致很多误解。

With the addition of default iterators in ECMAScript 6, the DOM definition of NodeList now specifically includes a default iterator that behaves in the same manner as the array default iterator. That means you can use NodeList in a for-of loop or any other place that uses an object’s default iterator. For example:

  ES6 增加了默认的迭代器,NodeList和数组一样也有默认的迭代器。这意味着你可以在任意可以使用对象迭代器的当防对NodeList使用for-of循环。例如:

var divs = document.getElementsByTagName("div");

for (let div of divs) {
    console.log(div.id);
}

 This code uses getElementsByTagName() method to retrieve a NodeList that represents all of the <div> elements in the document. The for-of loop then iterates over each element and outputs its ID, effectively making the code the same as it would be for a standard array.

  这段代码使用getElementsByTagName()方法来检索页面中的素有div元素。接着使用for-of循环来迭代每一个元素输出对应的id,这段代码的效率和使用标准函数是一样的。

Advanced Functionality-高级功能

There’s a lot that can be accomplished with the basic functionality of iterators and the convenience of creating them using generators. However, developers have discovered that iterators are much more powerful when used for tasks other than simply iterating over a collection of values. During the development of ECMAScript 6, a lot of unique ideas and patterns emerged that caused the addition of more functionality. Some of the changes are subtle, but when used together, can accomplish some interesting interactions.

   通过迭代器的基本功能已经可以完成很多任务,使用生成器也很容易创建他们。然后,开发者发现迭代器可以更加强大当不仅仅是用于迭代集合元素的时候。在开发ES6期间,出现了很多独特的想法和形式导致了迭代起更加强大的功能。一些改变很细微,但是当他们结合在一起的时候,可以完成一些非常有趣的交互。

Passing Arguments to Iterators-为迭代器传递参数

Throughout this chapter, you’ve seen that iterators can pass values out via the next() method or by using yield in a generator. It’s also possible to pass arguments into the iterator through the next() method. When an argument is passed to next(), it becomes the value of the yield statement inside a generator. For example:

  在本章中,你已经知道迭代器可以通过next方法或者生成器的yield来传递值。这样也可以通过next方法来给迭代器传递参数。当一个参数传递给next方式,它就变成了生成器中yield声明的值。例如:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;       // 4 + 2
    yield second + 3;                   // 5 + 3
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next(4));          // "{ value: 6, done: false }"
console.log(iterator.next(5));          // "{ value: 8, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

The first call to next() is a special case where any argument passed to it is lost. Since arguments passed to next() become the value returned byyield, there would have to be a way to access that argument before the first yield in the generator function. That’s not possible, so there’s no reason to pass an argument the first time next() is called.

  第一次调用next任何传递的参数都会丢失是一个特例,因为传递给next的参数变成了yield返回的值,那么在生成器函数中的第一次yield前必须有一个方法来取得参数。这是不可能的,所以不需要给第一次调用的next传递参数。

On the second call to next(), the value 4 is passed as the argument. The 4 ends up assigned to the variable first inside the generator function. In ayield statement including an assignment the right side of the expression is evaluated on the first call to next() and the left side is evaluated on the second call to next() before the function continues executing. Since the second call to next() passes in 4, that value is assigned to first and then execution continues.

  第二次调用next方法,参数的值为4。数值4最终赋给了生成器函数中的变量first。在yield声明中,右边的表达式等于第一次调用next的值,左边等于在函数继续执行之前第二次调用next的值。因为第二次调用传入了4,这个值赋给了first然后继续执行。

The second yield uses the result of the first yield and adds two, which means it returns a value of six. When next() is called a third time, the value 5is passed as an argument. That value is assigned to the variable second and then used in the third yield statement to return eight.

  第二个yield使用了第一次yield 的结果加上2,返回6.当第三次调用next时,5传入进来。zhege参数值付给了变量second,第三次yield返回8.

It’s a bit easier to think about what’s happening by considering which code is executing each time execution continues inside the generator function. Figure 6-1 uses colors to show the code being executed before yielding.

  想想代码每次在生成器内部执行的之后发生了什么比较容易。图6-1使用了颜色来演示在yield之前代码执行的过程。

 

The color yellow represents the first call to next() and all of the code that is executed inside of the generator as a result; the color aqua represents the call to next(4) and the code that is executed; the color purple represents the call to next(5) and the code that is executed as a result. The tricky part is the code on the right side of each expression executing and stopping before the left side is executed. This makes debugging complicated generators a bit more involved than regular functions.

   黄色代表第一次调用next在生成器内部中所执行的所有代码;浅绿色代表调用next(4)所执行的代码。紫色代表调用next(5)所执行的代码。重点在于每次代码的右侧的表达式执行和停止在左侧表达式执行完之前。这使得调试复杂的生成器比普通函数更难。

Throwing Errors in Iterators-迭代器中抛出错误

It’s not only possible to pass data into iterators, it’s also possible to pass error conditions. Iterators can choose to implement a throw() method that instructs the iterator to throw an error when it resumes. You can pass in an error object that should be thrown when the iterator continues processing. For example:

  不仅可以为迭代器传递数据,也可以传递错误条件。迭代起可以选择执行一个throw()方法来命令迭代器抛出一个错误。你可以传入一个错误的对象,当迭代器持续执行的时候抛出这个对象。例如:

function *createIterator() {
    let first = yield 1;
    let second = yield first + 2;       // yield 4 + 2, then throw
    yield second + 3;                   // never is executed
}

let iterator = createIterator();

console.log(iterator.next());                   // "{ value: 1, done: false }"
console.log(iterator.next(4));                  // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // error thrown from generator

 In this example, the first two yield expressions are evaluated as normal, but when throw() is called, an error is thrown before let second is evaluated. This effectively halts code execution similar to directly throwing an error. The only difference is the location in which the error is thrown. Figure 6-2 shows which code is executed at each step.

  在这个例子中,前两个yield表达式正常执行,但是当调用throw的时候,在let second执行之前错误就抛出了。类似于直接抛出错误,这有效的停止了代码的执行。唯一的区别在于错误抛出的位置。图6-2演示了代码每一步执行的情况。

In this figure, the color red represents the code executed when throw() is called and the red star shows approximately when the error is thrown inside the generator. The first two yield statements are evaluated fine, it’s only when throw() is called that an error is thrown before any other code is executed. Knowing this, it’s possible to catch such errors inside the generator using a try-catch block, such as:

  在此图中,红色代表了当throw调用的时候的代码执行,红色的星星大致表示了在生成器内部当错误抛出的时候。前两个yield正常执行,只有当throw被调用时,抛出错误在任何代码执行之前。知道了这个,可以在生成器中使用try-catch来捕获错误。例如:

function *createIterator() {
    let first = yield 1;
    let second;

    try {
        second = yield first + 2;       // yield 4 + 2, then throw
    } catch (ex) {
        second = 6;                     // on error, assign a different value
    }
    yield second + 3;
}

let iterator = createIterator();

console.log(iterator.next());                   // "{ value: 1, done: false }"
console.log(iterator.next(4));                  // "{ value: 6, done: false }"
console.log(iterator.throw(new Error("Boom"))); // "{ value: 9, done: false }"
console.log(iterator.next());            // "{ value: undefined, done: true }"

 In this example, a try-catch block is wrapped around the second yield statement. While this yield executes without error, the error is thrown before any value can be assigned to second, so the catch block assigns it a value of six. Execution then flows to the next yield and returns nine.

  在这个例子中,try-catch块包含了第二个yield声明,虽然这个yield执行没有错误,错误的抛出会在任何代码赋给second之前,所以catch模块赋值6,执行流向下一个yield,返回9.

You’ll also notice something interesting happened - the throw() method returned a value similar to that returned by next(). Because the error was caught inside the generator, code execution continued on to the next yield and returned the appropriate value.

  你也发现了当throw方法返回和next方法返回类似的值。因为错误是在生成器内部捕获的,代码会继续执行到下一个yield返回适当的值。

It helps to think of next() and throw() as both being instructions to the iterator: next() instructs the iterator to continue executing (possibly with a given value) and throw() instructs the iterator to continue executing by throwing an error. What happens after that point depends on the code inside the generator.

  可以把next方法和throw当作迭代器的指令:next命令迭代器继续执行,throw命令迭代器通过抛出错误来继续执行。在这之后会发生什么取决于生成器内部的代码。

Generator Return Statements - 生成器返回声明

Since generators are functions, you can use the return statement both to exit early and to specify a return value for the last call to next(). For most of this chapter you’ve seen examples where the last call to next() on an iterator returns undefined. It’s possible to specify an alternate value by using return as you would in any other function. In a generator, return indicates that all processing is done, so the done property is set to true and the value, if provided, becomes the value field. Here’s an example that simply exits early using return:

  因为生成器也是函数,你可以使用return声明来提前退出和制定最后一次next调用返回的值。本章中的很多例子中,最后一个调用next都返回undefined。可以使用return来返回一个替代的值。在生成器中,return表明所有的执行都结束,所以done属性设置为true,return 的值,如果指定了,就是value属性的值。例子:

function *createIterator() {
    yield 1;
    return;
    yield 2;
    yield 3;
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

 

In this code, the generator has a yield statement followed by a return statement. The return indicates that there are no more values to come and so the rest of the yield statements will not execute (they are unreachable).

 在这段代码中,生成器的yield声明之后有一个return声明。return表明接下来没有任何值,所以剩下的yield声明不会被执行。

You can also specify a return value that will end up in the value field of the returned object. For example:

  你也可以制定return的值,例如:

function *createIterator() {
    yield 1;
    return 42;
}

let iterator = createIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 42, done: true }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

Here, the value 42 is returned in the value field on the second call to next() (which is the first time that done is true). The third call to next() returns an object whose value property is once again undefined. Any value you specify with return is only available on the returned object one time before thevalue field is reset to undefined.

  这里,第二次调用next方法,42在value域中返回。第三次调用next返回一个对象-value属性再一次为undefined。return指定的值只能在value值域赋值为undefined之前,返回的对象中出现一次。

 


 

Any value specified by return is ignored by for-of.

注意:在for-of循环中会忽略return指定的值。


Delegating Generators-委派生成器

In some cases it may be useful to combine the values from two iterators into one. Using generators, it’s possible to delegate to another generator using a special form of yield with a star (*). As with generator definitions, it doesn’t matter where the star appears so as long as it is between the keyword yield and the generator function name. Here’s an example:

  在一些例子中,可能会将两个迭代器的值合并为一个。使用生成器,可以委托给另一个生成器通过使用特殊的带有星号的yield形式。生成器定义时,星号出现在哪里并没有关系,只要它在关键字yield和生成器名字之间。例子:

function *createNumberIterator() {
    yield 1;
    yield 2;
}

function *createColorIterator() {
    yield "red";
    yield "green";
}

function *createCombinedIterator() {
    yield *createNumberIterator();
    yield *createColorIterator();
    yield true;
}

var iterator = createCombinedIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: "red", done: false }"
console.log(iterator.next());           // "{ value: "green", done: false }"
console.log(iterator.next());           // "{ value: true, done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }"

 In this example, the createCombinedIterator() generator delegates first to createNumberIterator() and then to createColorIterator(). The returned iterator appears, from the outside, to be one consistent iterator that has produced all of the values. Each call to next() is delegated to the appropriate iterator until they are empty, and then the final yield is executed to return true.

  在这个例子中,createCombinedIterator生成器首先委托给了createNumberIterator()接着委托给了createColorIterator()。返回的迭代器输出所有的值,每次调用next都会委托给适当的迭代器直到为空。最后的yield执行返回true。

Notice that the value 3 was never output from any call to next(), it existed solely inside of createCombinedIterator(). It is possible to output that value as well by adding another yield statement, such as:

  注意,数值3在任何的next调用中都没有被输出,它只单独的存在于createCombinedIterator()中。可以通过增加另一个yield声明来输出这个值,例如:

function *createNumberIterator() {
    yield 1;
    yield 2;
    return 3;
}

function *createRepeatingIterator(count) {
    for (let i=0; i < count; i++) {
        yield "repeat";
    }
}

function *createCombinedIterator() {
    let result = yield *createNumberIterator();
    yield result;
    yield *createRepeatingIterator(result);
}

var iterator = createCombinedIterator();

console.log(iterator.next());           // "{ value: 1, done: false }"
console.log(iterator.next());           // "{ value: 2, done: false }"
console.log(iterator.next());           // "{ value: 3, done: false }"
console.log(iterator.next());           // "{ value: "repeat", done: false }"
console.log(iterator.next());           // "{ value: "repeat", done: false }"
console.log(iterator.next());           // "{ value: "repeat", done: false }"
console.log(iterator.next());           // "{ value: undefined, done: true }" 

In this code, the extra yield statement explicitly outputs the returned value from createNumberIterator().

  在这段代码中,另一个yield声明明确的在createNumberIterator中输出返回值。

Generator delegation using the return value is a very powerful paradigm that allows for some very interesting possibilities, especially when used in conjunction with asynchronous operations.

   通常委托使用return是很强大的,可能会产生很有趣的范例,尤其是在和异步操作结合起来的时候。

 


You can use yield * directly on strings, such as yield * "hello" and the string’s default iterator will be used.

你可以直接在字符串上使用yield*,例如:yield * "hello",字符串默认的迭代器会被使用。


Asynchronous Task Scheduling-异步任务调度

A lot of the excitement around generators is directly related to usage with asynchronous programming. Asynchronous programming in JavaScript is a double-edged sword: it’s very easy to do simple things while complex things become an errand in code organization. Since generators allow you to effectively pause code in the middle of execution, this opens up a lot of possibilities as it relates to asynchronous processing.

  生成器令人兴奋的功能是直接直接用于异步编程。在js中,异步编程是一个双刃剑,它很容易做一些简单的事情,然而复杂的事情会造成组织代码的困难。因为生成器允许你在执行中间暂停代码,当涉及到异步处理时,增加了很多可能性。

The traditional way to perform asynchronous operations is to call a function that has a callback. For example, consider reading a file from disk in Node.js:

   传统方式来执行一个异步操作时调用回调函数。例如,Nodejs中在磁盘中读取文件:

var fs = require("fs");

function readConfigFile(callback) {
    fs.readFile("config.json", callback);
}

function init(callback) {
    readConfigFile(function(err, contents) {
        if (err) {
            throw err;
        }

        doSomethingWith(contents);
        console.log("Done");
    });
}

init();

 Instead of providing a callback, you can yield and just wait for a response before starting again:

  你可以使用yield等待响应来重新执行来替代提供一个回调函数,

var fs = require("fs");

var task;

function readConfigFile() {
    fs.readFile("config.json", function(err, contents) {
        if (err) {
            task.throw(err);
        } else {
            task.next(contents);
        }
    });
}

function *init() {
    var contents = yield readConfigFile();
    doSomethingWith(contents);
    console.log("Done");
}

task = init();
task.next();

 The difference between init() in this example and the previous one is why developers are excited about generators for asynchronous operation. Instead of using callbacks, init() yields to readConfigFile(), which does the asynchronous read operation and, when complete, either calls throw() if there’s an error or next() if the contents have been ready. That means the yield operation inside of init() will throw an error if there’s a read error or else the file contents will be returned almost as if the operation was synchronous.

  本例中init()和之前例子中的区别就是为什么开发者对生成器来执行异步操作如此兴奋的原因。init()调用readConfigFile(),来执行异步的读取操作,当完成的时候,要么时调用throw如果存在错误的话,或者调用next如果内容已经准备好。这意味着,init内部的yield操作,当存在错误的时候抛出错误,或者文件内容像同步操作一样被返回。

Managing the task variable is a bit cumbersome in this example, but it’s only important that you understand the theory. 

  管理任务变量在本例中有点麻烦,但是重点时你理解了理论。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2015-03-17 15:32  丁家小花花  阅读(462)  评论(0编辑  收藏  举报