Iterators and generators

Esta traducción está incompleta. Por favor, ayuda a traducir este artículo del inglés.

Procesar cada uno de los elementos en una colección es un tipo de operación muy común. JavaScript proporciona diversas formas de iterar a través de los elementos de una colección, desde simples bucles for hasta map(), y filter(). Los iteradores y los generadores acercan el concepto de iteración directamente al núcleo del lenguaje y proporcionan un mecanismo para personalizar el comportamiento de los bucles for...of.

Para más información, también puedes ver:

Iteradores

Un objeto es un iterador cuando sabe como acceder a los elementos de una colleccion, mientras mantiene un registro de su posición actual dentro de esa secuencia. En JavaScript un iterador es un objeto que proporciona un método next() que devuelve el siguiente elemento en la secuencia. Este método devuelve un objeto con dos propiedades: done y value.

Una vez creado, un objeto iterador puede utilizarse explícitamente llamando repetidamente al método  next().

function makeIterator(array){
    var nextIndex = 0;
    
    return {
       next: function(){
           return nextIndex < array.length ?
               {value: array[nextIndex++], done: false} :
               {done: true};
       }
    }
}

Una vez inicializado, puede llamar al método next() para acceder a su vez,  a parejas de valores clave-valor desde el objeto:

var it = makeIterator(['yo', 'ya']);
console.log(it.next().value); // 'yo'
console.log(it.next().value); // 'ya'
console.log(it.next().done);  // true

Iterables

Un objeto es iterable si define el comportamiento de su iteración, como por ejemplo qué valores que recorren un bucle  for..of. Algunos tipos de datos, como Array or Map, incorporan esta caracteristica por defecto,  mientras que otras no (como Object).

Con el fin de ser iterable, un objeto debe implementar el método @@iterator, lo que significa que el objeto  (or one of the objects up its prototype chain) debe tener una propiedad con la clave Symbol.iterator:

Iterables definidos-por-el-usuario

Podemos hacer nuestros propios iterables de este modo:

var myIterable = {}
myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
};
[...myIterable] // [1, 2, 3]

Built-in iterables

String, Array, TypedArray, Map y Set son iterables, porque el prototype objects de todos ellos tiene un método Symbol.iterator.

Syntaxes expecting iterables

Algunas sentencias y expresiones esperan iterables, por ejemplo el bucle for-of, spread operator, yield*, y destructuring assignment.

for(let value of ["a", "b", "c"]){
    console.log(value)
}
// "a"
// "b"
// "c"

[..."abc"] // ["a", "b", "c"]

function* gen(){
  yield* ["a", "b", "c"]
}

gen().next() // { value:"a", done:false }

[a, b, c] = new Set(["a", "b", "c"])
a // "a"

Generators

While custom iterators are a useful tool, their creation requires careful programming due to the need to explicitly maintain their internal state. Generators provide a powerful alternative: they allow you to define an iterative algorithm by writing a single function which can maintain its own state.

A generator is a special type of function that works as a factory for iterators. A function becomes a generator if it contains one or more yield expressions and if it uses the function* syntax.

function* idMaker(){
  var index = 0;
  while(true)
    yield index++;
}

var gen = idMaker();

console.log(gen.next().value); // 0
console.log(gen.next().value); // 1
console.log(gen.next().value); // 2
// ...

Advanced generators

Generators compute their yielded values on demand, which allows them to efficiently represent sequences that are expensive to compute, or even infinite sequences as demonstrated above.

The next() method also accepts a value which can be used to modify the internal state of the generator. A value passed to next() will be treated as the result of the last yield expression that paused the generator.

Here is the fibonacci generator using next(x) to restart the sequence:

function* fibonacci(){
  var fn1 = 1;
  var fn2 = 1;
  while (true){  
    var current = fn2;
    fn2 = fn1;
    fn1 = fn1 + current;
    var reset = yield current;
    if (reset){
        fn1 = 1;
        fn2 = 1;
    }
  }
}

var sequence = fibonacci();
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
console.log(sequence.next().value);     // 5
console.log(sequence.next().value);     // 8
console.log(sequence.next().value);     // 13
console.log(sequence.next(true).value); // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2
console.log(sequence.next().value);     // 3
Note: As a point of interest, calling next(undefined) is equivalent to calling next(). However, starting a newborn generator with any value other than undefined when calling next() will result in a TypeError exception.

You can force a generator to throw an exception by calling its throw() method and passing the exception value it should throw. This exception will be thrown from the current suspended context of the generator, as if the yield that is currently suspended were instead a throw value statement.

If a yield is not encountered during the processing of the thrown exception, then the exception will propagate up through the call to throw(), and subsequent calls to next() will result in the done property being true.

Generators have a return(value) method that returns the given value and finishes the generator itself.

Generator comprehensions

A significant drawback of array comprehensions is that they cause an entire new array to be constructed in memory. When the input to the comprehension is itself a small array the overhead involved is insignificant — but when the input is a large array or an expensive (or indeed infinite) generator the creation of a new array can be problematic.

Generators enable lazy computation of sequences, with items calculated on-demand as they are needed. Generator comprehensions are syntactically almost identical to array comprehensions — they use parentheses instead of braces— but instead of building an array they create a generator that can execute lazily. You can think of them as short hand syntax for creating generators.

Suppose we have an iterator it which iterates over a large sequence of integers. We want to create a new iterator that will iterate over their doubles. An array comprehension would create a full array in memory containing the doubled values:

var doubles = [for (i in it) i * 2];

A generator comprehension on the other hand would create a new iterator which would create doubled values on demand as they were needed:

var it2 = (for (i in it) i * 2);
console.log(it2.next()); // The first value from it, doubled
console.log(it2.next()); // The second value from it, doubled

When a generator comprehension is used as the argument to a function, the parentheses used for the function call means that the outer parentheses can be omitted:

var result = doSomething(for (i in it) i * 2);

The significant difference between the two examples being that by using the generator comprehension, you would only have to loop over the 'obj' structure once, total, as opposed to once when comprehending the array, and again when iterating through it.

Para obtener más información , puedes ver Generator comprehensions.

Etiquetas y colaboradores del documento

Etiquetas: 
 Colaboradores en esta página: eycopia, nefter, dieguezz, Breaking_Pitt
 Última actualización por: eycopia,