Les itérateurs et les générateurs

ES6 introduit deux nouvelles façons de parcourir des données à travers les itérateurs et les générateurs.

Les itérateurs

Avant ES6, il n’existait pas réellement de manière unifiée et simple de parcourir des éléments d’une collection quelconque. ES6 introduit le protocole de l’itérateur. Un itérateur sert à parcourir un ensemble d’éléments de manière indépendante de la structure sous-jacente de la collection.

En JavaScript, un itérateur est un objet qui dispose de la méthode next. À chaque appel, cette méthode retourne un objet avec l’attribut value qui représente la valeur courante et l’attribut done qui est une valeur booléenne qui vaut true lorsque l’on a dépassé le dernier élément (dans ce cas value est non défini).

let data = ["a", "e", "i", "o", "u", "y"];

let monIterateur = {
    current: 0,
    next() {
        if (this.current >= data.length) {
            return {done: true};
        } else {
            return {value: data[current++], done: false};
        }
    }
}

Les itérables

Un itérable doit fournir une méthode @@iterator, c’est-à-dire qu’il doit posséder une méthode Symbol.iterator qui retourne un itérateur.

let voyelles = {
    data: ["a", "e", "i", "o", "u", "y"],
    [Symbol.iterator]() {
        return {
            data: this.data,
            current: 0,
            next() {
                if (this.current >= this.data.length) {
                    return {done: true};
                } else {
                    return {value: this.data[this.current++], done: false};
                }
            }
        }
    }
};

La structure for … of

Une nouvelle structure de contrôle for a été ajoutée avec ES6 pour permettre de parcourir les objets itérables. Il s’agit de la structure for ... of

for (let v of voyelles) {
    console.log(v);
}

// Affiche
// a
// e
// i
// o
// u
// y

Les tableaux et les chaînes de caractères sont des objets itérables. Il est donc très facile de les parcourir avec cette nouvelle structure de contrôle.

for (let i of [1, 2, 3, 4]) {
    console.log(i);
}

for (let l of "Bonjour") {
    console.log(l);
}

Les générateurs

Un générateur est une fonction particulière qui retourne non pas un seul résultat, mais qui produit une série de valeurs comme si son exécution était suspendue jusqu’au prochain appel. Ce type de fonctions est très pratique pour générer une suite de valeurs, d’où son nom de générateur.

Un générateur est déclaré avec l’expression function*. Lorsque le générateur veut interrompre temporairement son exécution et produire un résultat, il le fait en utilisant le mot-clé yield.

function* suite() {
    yield 1;
    yield 2;
    yield 3;
}

Un générateur peut également être utilisé dans une structure for ... of.

for (let n of suite()) {
    console.log(n);
}

// Affiche
// 1
// 2
// 3

Un générateur est un itérable. Donc, il est possible d’appeler directement un générateur et de récupérer un itérateur. On alors appeler sa méthode next

let i = suite();
console.log(i.next().value);
console.log(i.next().value);
console.log(i.next().value);

// Affiche
// 1
// 2
// 3

Il est également possible d’injecter une valeur lors du passage au suivant en passant une valeur à la méthode next. Cette valeur est récupérable pas le générateur en récupérant le résultat de l’instruction yield.

function* mon_generateur() {
    let resultat;
    while (resultat != "ok") {
        resultat = yield "Pas bon";
    }
    yield "Bon";
}

let g = mon_generateur();
console.log(g.next().value);
// Affiche "Pas bon"
console.log(g.next("ko").value);
// Affiche "Pas bon"
console.log(g.next("ok").value);
// Affiche "Bon"

Note

Il est également possible de forcer l’arrêt du générateur en appelant la méthode return et en passant la valeur de retour.

Il est également possible de forcer l’arrêt du générateur par une exception en appelant la méthode throw et en passant le message de l’erreur.

Les générateurs permettent notamment de générer des suites d’éléments sans empreinte mémoire car on ne passe pas par la création d’un tableau. Par exemple, on peut facilement écrire un générateur qui énumère les nombres pairs :

function* nombres_pairs() {
    for(let pair = 2;; pair += 2) {
        yield pair;
    }
}