Kodokon kodokon.com

Itérateurs, générateurs et protocoles d'itération

Maîtrisez les protocoles qui alimentent for...of, le spread et la déstructuration, puis exploitez les générateurs pour produire des séquences paresseuses.

9 min · 3 questions

Ouvrir cette leçon dans Kodokon

Deux protocoles distincts régissent l'itération. Un objet est itérable s'il expose une méthode sous la clé Symbol.iterator ; cette méthode doit renvoyer un itérateur, c'est-à-dire un objet doté d'une méthode next() retournant { value, done }. Tout ce qui consomme des séquences - for...of, le spread ..., la déstructuration, Array.from, Promise.all - passe par ces protocoles. Vous pouvez donc rendre n'importe quel objet compatible.

JAVASCRIPT
const range = {
  from: 1,
  to: 3,
  [Symbol.iterator]() {
    let current = this.from;
    const last = this.to;
    return {
      next() {
        return current <= last
          ? { value: current++, done: false }
          : { value: undefined, done: true };
      }
    };
  }
};

console.log([...range]); // [1, 2, 3]
Un itérable écrit à la main, sans classe ni générateur.

Écrire des itérateurs à la main est verbeux. Les générateurs (function*) produisent un itérateur automatiquement : chaque yield suspend l'exécution et conserve l'état local de la fonction. Cette évaluation paresseuse permet de modéliser des séquences infinies, tant qu'un consommateur limite le nombre de valeurs demandées.

JAVASCRIPT
function* naturals() {
  let n = 0;
  while (true) yield n++;
}

function* take(iterable, count) {
  let index = 0;
  for (const value of iterable) {
    if (index >= count) return;
    yield value;
    index += 1;
  }
}

console.log([...take(naturals(), 4)]); // [0, 1, 2, 3]
Séquence infinie consommée sans jamais bloquer.

Les itérateurs de générateur exposent aussi return() et throw(). Point de spécification souvent ignoré : quand vous quittez une boucle for...of avec break, return ou une exception, le moteur appelle automatiquement iterator.return(). Dans un générateur, cela exécute les blocs finally en attente - l'endroit idéal pour libérer une ressource (fichier, connexion, verrou).

JAVASCRIPT
function* readLines() {
  try {
    yield "line 1";
    yield "line 2";
  } finally {
    console.log("cleanup");
  }
}

for (const line of readLines()) {
  console.log(line);
  break; // declenche return(), donc le finally
}
// line 1, cleanup
break appelle return() : le finally s'exécute.

Quiz de validation

Vérifiez que vous avez bien retenu les points clés de cette leçon.

  1. Qu'est-ce qui rend un objet itérable au sens du protocole ?
    • Posséder une propriété length et des index numériques
    • Exposer une méthode Symbol.iterator qui renvoie un objet doté de next()
    • Hériter de Array.prototype
    • Posséder directement une méthode next()
  2. Que se passe-t-il quand vous sortez d'une boucle for...of avec break alors qu'elle consomme un générateur ?
    • Rien de particulier, le générateur reste suspendu au dernier yield
    • Le moteur appelle throw() sur l'itérateur
    • Le moteur appelle return(), ce qui exécute les blocs finally du générateur
    • Le générateur repart du début à la prochaine itération
  3. Pourquoi peut-on manipuler un générateur infini sans bloquer le programme ?
    • Le moteur limite automatiquement le nombre d'itérations
    • Les générateurs s'exécutent dans un thread séparé
    • L'évaluation est paresseuse : chaque valeur n'est calculée qu'à l'appel de next()
    • Les valeurs sont pré-calculées et mises en cache par le moteur