Avancé30 min de lecture

Itérateurs & Générateurs

Comprends le protocole d'itération et crée des itérables personnalisés avec les fonctions génératrices en utilisant function* et yield.

Le Protocole d'Itération

Un itérateur est tout objet qui implémente une méthode next(). Chaque appel à next() retourne un objet avec deux propriétés :

  • value — la prochaine valeur dans la séquence.
  • donetrue si la séquence est terminée, false sinon.
javascript
// Un itérateur manuel simple
const iterator = {
  current: 1,
  last: 3,
  next() {
    if (this.current <= this.last) {
      return { value: this.current++, done: false };
    }
    return { value: undefined, done: true };
  }
};

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 }

De nombreux types JavaScript intégrés sont itérables : tableaux, chaînes de caractères, Maps, Sets, NodeLists, et plus encore. La boucle for...of, l'opérateur de décomposition (...), et la déstructuration utilisent tous le protocole d'itération en coulisses.

javascript
for (const char of 'hello') {
  console.log(char); // 'h', 'e', 'l', 'l', 'o'
}

Le Protocole Iterable

Un objet est itérable s'il a une méthode à la clé Symbol.iterator qui retourne un itérateur.

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

Maintenant tu peux utiliser cet itérable personnalisé avec n'importe quelle fonctionnalité d'itération :

javascript
// for...of
for (const n of range) {
  console.log(n); // 1, 2, 3, 4, 5
}

// Opérateur de décomposition
const nums = [...range]; // [1, 2, 3, 4, 5]

// Déstructuration
const [a, b, c] = range; // a=1, b=2, c=3

// Array.from
const arr = Array.from(range); // [1, 2, 3, 4, 5]

Le protocole iterable est ce qui rend les fonctionnalités d'itération de JavaScript si flexibles et cohérentes. Tout objet qui implémente [Symbol.iterator]() peut participer à l'écosystème d'itération.

Fonctions Génératrices

Écrire des itérateurs manuellement est verbeux. Les fonctions génératrices offrent une façon beaucoup plus simple de créer des itérateurs.

Une fonction génératrice est déclarée avec function* (note l'astérisque). À l'intérieur, tu utilises le mot-clé yield pour produire des valeurs.

javascript
function* countUp(start, end) {
  for (let i = start; i <= end; i++) {
    yield i;
  }
}

const gen = countUp(1, 3);
console.log(gen.next()); // { value: 1, done: false }
console.log(gen.next()); // { value: 2, done: false }
console.log(gen.next()); // { value: 3, done: false }
console.log(gen.next()); // { value: undefined, done: true }

Caractéristiques clés :

  • yield met en pause la fonction et produit une valeur.
  • Appeler next() reprend l'exécution là où elle s'était arrêtée.
  • Les générateurs sont paresseux — les valeurs sont calculées à la demande, pas toutes à la fois.
  • Les générateurs sont itérables — ils implémentent automatiquement les deux protocoles.
javascript
for (const n of countUp(1, 5)) {
  console.log(n); // 1, 2, 3, 4, 5
}

const nums = [...countUp(10, 15)]; // [10, 11, 12, 13, 14, 15]

yield* délègue à un autre itérable :

javascript
function* combined() {
  yield* [1, 2, 3];
  yield* 'abc';
}
// Produit : 1, 2, 3, 'a', 'b', 'c'

Cas d'Usage

Les générateurs sont particulièrement puissants pour les scénarios qui bénéficient de l'évaluation paresseuse — produire des valeurs uniquement quand elles sont nécessaires.

Séquences infinies — génère des valeurs indéfiniment, en les consommant une à la fois :

javascript
function* ids() {
  let id = 1;
  while (true) {
    yield id++;
  }
}

const idGen = ids();
console.log(idGen.next().value); // 1
console.log(idGen.next().value); // 2
console.log(idGen.next().value); // 3
// Peut continuer indéfiniment

Séquence de Fibonacci :

javascript
function* fibonacci() {
  let [a, b] = [0, 1];
  while (true) {
    yield a;
    [a, b] = [b, a + b];
  }
}

Traitement paresseux de grands ensembles de données — évite de tout charger en mémoire :

javascript
function* filterLarge(items, predicate) {
  for (const item of items) {
    if (predicate(item)) yield item;
  }
}

Pagination :

javascript
function* paginate(items, pageSize) {
  for (let i = 0; i < items.length; i += pageSize) {
    yield items.slice(i, i + pageSize);
  }
}

Générateurs asynchrones combinent les générateurs avec async/await pour diffuser des données asynchrones :

javascript
async function* fetchPages(url) {
  let page = 1;
  while (true) {
    const response = await fetch(`${url}?page=${page}`);
    const data = await response.json();
    if (data.length === 0) return;
    yield data;
    page++;
  }
}

Générateur en Action

html
<div id="output"></div>

<script>
  // Générateur Fibonacci
  function* fibonacci() {
    let [a, b] = [0, 1];
    while (true) {
      yield a;
      [a, b] = [b, a + b];
    }
  }

  // Prend les N premières valeurs d'un générateur
  function take(gen, n) {
    const result = [];
    for (const value of gen) {
      result.push(value);
      if (result.length >= n) break;
    }
    return result;
  }

  const first10 = take(fibonacci(), 10);

  // Générateur range
  function* range(start, end, step = 1) {
    for (let i = start; i <= end; i += step) {
      yield i;
    }
  }

  const evens = [...range(0, 20, 2)];

  const output = document.getElementById('output');
  output.innerHTML = `
    <p>Les 10 premiers Fibonacci : [${first10.join(', ')}]</p>
    <p>Nombres pairs 0-20 : [${evens.join(', ')}]</p>
  `;
</script>

Que fait le mot-clé yield dans un générateur ?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.