Traduzione in corso.

Processare ognuno degli elementi in una collezione è un'operazione molto comune. JavaScript fornisce tutta una serie di costrutti per iterare una collezione di elementi, dai semplici cicli for a map()filter(). Iterators e Generators portano il concetto di iterazione direttamente al cuore del linguaggio e forniscono un meccanismo per personalizzare i cicli for...of.

Per maggiori dettagli, vedi anche:

Iterators

Un oggetto è un iterator quando sa come accedere agli elementi di una collezione uno per volta, conservando l'informazione sulla sua posizione corrente nella sequenza. In Javascript un iterator è un oggetto che implementa il metodo next() , il quale ritorna l'elemento successivo della sequenza. Questo metodo ritorna un oggetto con due proprietà: done evalue.

Una volta che è stato creato, un iterator può essere utlizzato esplicitamente chiamando più volte il metodo next().

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

Una volta che un iterator è stato inizializzato, il metodonext() può essere chiamato per accedere a coppie chiave-valore dall'oggetto ritornato:

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

Generators

Nonostante implementare un iterator possa essere utile, richiede una considerevole attenzione nella programmazione a causa del bisogno esplicito di mantenere lo stato interno dell'iteratore. I Generators forniscono una potente alternativa: ti permettono di definire un algoritmo iterativo scrivendo una singola funzione in grado di mantenere il proprio stato.

Una GeneratorFunction è uno speciale tipo di funzione che opera come una "fabbrica" di iterators. Quando viene eseguita ritorna un nuovo oggetto Generator. Una funzione diventa una GeneratorFunction se usa la sintassi function*.

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
// ...

Iterables

Un oggetto è un iterable se definisce un comportamento di iterazione, come per esempio quali valori sono considerati in un costrutto for...of. Alcuni tipi built-in di Javascript (come ArrayMap) hanno un comportamento predefinito, mentre altri tipi (come Object) non ce l'hanno.

Affinché un oggetto possa essere considerato un iterable deve implementare il metodo @@iterator, cioè l'oggetto (o uno degli oggetti che lo precedono nella catena dei prototipi) deve avere una proprietà con una chiave Symbol.iterator.

Iterables definiti dall'utente

Possiamo creare i nostri iterables in questo modo:

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

for (let value of myIterable) { 
    console.log(value); 
}
// 1
// 2
// 3

or

[...myIterable]; // [1, 2, 3]

Iterables Built-in

String, Array, TypedArray, MapSet sono tutti iterables built-in nel linguaggio, perché i loro oggetti prototipi hanno tutti un metodo Symbol.iterator.

Sintassi che si aspettano degli iterables

Alcuni costrutti ed espressioni si aspettano degli iterables, per esempio il ciclo for-of, la sintassi spread, yield*, e l'assegnamento di destrutturazione.

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 avanzati

I generators calcolano i loro valori solo quando vengono effettivamente richiesti, il che gli permette di rappresentare sequenze che sono troppo onerose da calcolare, o perfino sequenze infinite, come nella funzione idMaker().

Il metodo next() accetta anche un valore che può essere utilizzato per modificare lo stato interno di un generatore. Un valore passato a next() sarà trattato come il risultato dell'ultima espressione yield che ha messo in pausa il generator.

Ecco un generator che produce una sequenza di numeri di Fibonacci. In questo generator il metodonext(x) può essere utilizzato per reinizializzare la sequenza:

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

var sequence = fibonacci();
console.log(sequence.next().value);     // 0
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(true).value); // 0
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 1
console.log(sequence.next().value);     // 2

Puoi forzare un generator a lanciare una eccezione chiamando il suo metodo throw() e passandogli il valore che dovrebbe lanciare. Questa eccezione sarà lanciata dal corrente context sospeso del generator, come se lo yield che è attualmente sospeso fosse invece una dichiarazione throw value.

Se non si incontra uno yield durante la risoluzione della eccezione lanciata, allora l'eccezione si propagherà fino alla chiamata athrow(), e in tutte le successive chiamate a next() la proprietà done saràtrue.

I generators hanno un metodo return(value) che ritorna un valore e termina il generator stesso.

Tag del documento e collaboratori

 Hanno collaborato alla realizzazione di questa pagina: jackdbd
 Ultima modifica di: jackdbd,