Back

Débuter avec les aides d'itérateurs JavaScript

Débuter avec les aides d'itérateurs JavaScript

Si vous avez déjà essayé de traiter un jeu de données massif en JavaScript, vous connaissez la difficulté. Les méthodes de tableau traditionnelles comme .map() et .filter() vous obligent à charger tout en mémoire d’un coup. Essayez cela avec un million d’enregistrements ou un flux de données infini, et votre application plante. Les aides d’itérateurs JavaScript résolvent ce problème en apportant l’évaluation paresseuse au cœur du langage.

Cet article vous montre comment utiliser les nouvelles méthodes d’aide d’itérateurs, comprendre leurs avantages en termes de performance, et les appliquer à des scénarios concrets comme le traitement de gros fichiers, la gestion de flux d’API, et le travail avec des séquences infinies.

Points clés à retenir

  • Les aides d’itérateurs fournissent une évaluation paresseuse pour un traitement de données efficace en mémoire
  • Convertissez les tableaux avec .values() et autres itérables avec Iterator.from()
  • Les méthodes comme .map(), .filter(), et .take() s’enchaînent sans créer de tableaux intermédiaires
  • Parfait pour les séquences infinies, gros fichiers, et données en flux
  • Usage unique seulement - créez de nouveaux itérateurs pour plusieurs itérations

Comprendre le protocole d’itérateur

Avant de plonger dans les nouvelles aides, clarifions ce qui rend les itérateurs spéciaux. Un itérateur est simplement un objet avec une méthode .next() qui retourne des paires {value, done} :

const iterator = {
  current: 0,
  next() {
    return this.current < 3 
      ? { value: this.current++, done: false }
      : { done: true }
  }
}

console.log(iterator.next()) // { value: 0, done: false }
console.log(iterator.next()) // { value: 1, done: false }

Les tableaux, Sets, Maps, et générateurs implémentent tous le protocole d’itérateur via leur méthode [Symbol.iterator](). Ce protocole alimente les boucles for...of et l’opérateur de décomposition, mais jusqu’à récemment, les itérateurs manquaient des méthodes de programmation fonctionnelle que les développeurs attendent.

Aides d’itérateurs JavaScript : les nouveautés

Les aides d’itérateurs étendent le prototype Iterator avec des méthodes qui reflètent les opérations de tableau mais fonctionnent de manière paresseuse :

MéthodeDescriptionRetourne
.map(fn)Transforme chaque valeurIterator
.filter(fn)Produit les valeurs qui passent le testIterator
.take(n)Produit les n premières valeursIterator
.drop(n)Ignore les n premières valeursIterator
.flatMap(fn)Mappe et aplatit les résultatsIterator
.reduce(fn, init)Agrège en une seule valeurValue
.find(fn)Première valeur passant le testValue
.some(fn)Teste si une valeur passeBoolean
.every(fn)Teste si toutes les valeurs passentBoolean
.toArray()Collecte toutes les valeursArray

Pour utiliser ces méthodes, convertissez d’abord votre structure de données en itérateur :

// Pour les tableaux
const result = [1, 2, 3, 4, 5]
  .values()  // Convertir en itérateur
  .filter(x => x % 2 === 0)
  .map(x => x * 2)
  .toArray()  // [4, 8]

// Pour les autres itérables
const set = new Set([1, 2, 3])
const doubled = Iterator.from(set)
  .map(x => x * 2)
  .toArray()  // [2, 4, 6]

Évaluation paresseuse vs empressée : la différence clé

Les méthodes de tableau traditionnelles traitent tout immédiatement :

// Empressée - traite tous les éléments immédiatement
const eager = [1, 2, 3, 4, 5]
  .map(x => {
    console.log(`Mapping ${x}`)
    return x * 2
  })
  .filter(x => x > 5)

// Affiche : Mapping 1, 2, 3, 4, 5
// Résultat : [6, 8, 10]

Les aides d’itérateurs traitent les valeurs seulement quand elles sont consommées :

// Paresseuse - traite seulement ce qui est nécessaire
const lazy = [1, 2, 3, 4, 5]
  .values()
  .map(x => {
    console.log(`Mapping ${x}`)
    return x * 2
  })
  .filter(x => x > 5)
  .take(2)

// Rien d'affiché encore !

const result = [...lazy]
// Affiche : Mapping 1, 2, 3
// Résultat : [6, 8]

Notez comment la version paresseuse s’arrête après avoir trouvé deux valeurs correspondantes, ne traitant jamais les éléments 4 et 5. Cette efficacité devient cruciale lors du travail avec de gros jeux de données.

Exemples pratiques et cas d’usage

Traitement de gros fichiers ligne par ligne

Au lieu de charger un fichier entier en mémoire :

async function* readLines(file) {
  const reader = file.stream().getReader()
  const decoder = new TextDecoder()
  let buffer = ''
  
  while (true) {
    const { done, value } = await reader.read()
    if (done) break
    
    buffer += decoder.decode(value, { stream: true })
    const lines = buffer.split('\n')
    buffer = lines.pop()
    
    for (const line of lines) yield line
  }
  if (buffer) yield buffer
}

// Traiter CSV sans charger le fichier entier
const validRecords = await readLines(csvFile)
  .drop(1)  // Ignorer l'en-tête
  .map(line => line.split(','))
  .filter(cols => cols[2] === 'active')
  .take(100)
  .toArray()

Travailler avec des séquences infinies

Générer et traiter des flux de données infinis :

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

// Trouver le premier nombre de Fibonacci supérieur à 1000
const firstLarge = fibonacci()
  .find(n => n > 1000)  // 1597

// Obtenir les 10 premiers nombres de Fibonacci pairs
const evenFibs = fibonacci()
  .filter(n => n % 2 === 0)
  .take(10)
  .toArray()

Pagination d’API sans surcharge mémoire

Gérer efficacement les API paginées :

async function* fetchAllUsers(apiUrl) {
  let page = 1
  while (true) {
    const response = await fetch(`${apiUrl}?page=${page}`)
    const { data, hasMore } = await response.json()
    
    for (const user of data) yield user
    
    if (!hasMore) break
    page++
  }
}

// Traiter les utilisateurs sans charger toutes les pages
const premiumUsers = await fetchAllUsers('/api/users')
  .filter(user => user.subscription === 'premium')
  .map(user => ({ id: user.id, email: user.email }))
  .take(50)
  .toArray()

Considérations de performance et usage mémoire

Les aides d’itérateurs brillent quand :

  • Traitement de données plus grandes que la mémoire disponible
  • Vous n’avez besoin que d’un sous-ensemble de résultats
  • Enchaînement de multiples transformations
  • Travail avec des flux ou données temps réel

Elles sont moins adaptées quand :

  • Vous avez besoin d’un accès aléatoire aux éléments
  • Le jeu de données est petit et déjà en mémoire
  • Vous devez itérer plusieurs fois (les itérateurs sont à usage unique)

Voici une comparaison mémoire :

// Approche tableau intensive en mémoire
function processLargeDataArray(data) {
  return data
    .map(transform)      // Crée un nouveau tableau
    .filter(condition)   // Crée un autre tableau
    .slice(0, 100)       // Crée un troisième tableau
}

// Approche itérateur efficace en mémoire
function processLargeDataIterator(data) {
  return data
    .values()
    .map(transform)      // Pas de tableau intermédiaire
    .filter(condition)   // Pas de tableau intermédiaire
    .take(100)
    .toArray()           // Seulement les 100 éléments finaux en mémoire
}

Support navigateur et polyfills

Les aides d’itérateurs JavaScript sont supportées dans :

  • Chrome 122+
  • Firefox 131+
  • Safari 18.4+
  • Node.js 22+

Pour les environnements plus anciens, utilisez le polyfill es-iterator-helpers :

npm install es-iterator-helpers

Pièges courants et solutions

Les itérateurs sont à usage unique

const iter = [1, 2, 3].values().map(x => x * 2)
console.log([...iter])  // [2, 4, 6]
console.log([...iter])  // [] - Déjà consommé !

// Solution : Créer un nouvel itérateur
const makeIter = () => [1, 2, 3].values().map(x => x * 2)

Mélanger les méthodes d’itérateur et de tableau

// Ne fonctionnera pas - filter retourne un itérateur, pas un tableau
const result = [1, 2, 3]
  .values()
  .filter(x => x > 1)
  .includes(2)  // Erreur !

// Solution : Convertir d'abord en tableau
const result = [1, 2, 3]
  .values()
  .filter(x => x > 1)
  .toArray()
  .includes(2)  // true

Conclusion

Les aides d’itérateurs JavaScript apportent la programmation fonctionnelle à l’évaluation paresseuse, rendant possible le traitement efficace de jeux de données larges ou infinis. En comprenant quand utiliser .values() ou Iterator.from() et comment l’évaluation paresseuse diffère des méthodes de tableau empressées, vous pouvez écrire du code efficace en mémoire qui passe à l’échelle. Commencez à utiliser ces méthodes pour les données en flux, la pagination, et tout scénario où charger tout en mémoire n’est pas pratique.

FAQ

Les aides d'itérateurs standards fonctionnent seulement avec des itérateurs synchrones. Pour les opérations asynchrones, vous devrez attendre les aides d'itérateurs asynchrones (proposées pour les futures versions ES) ou utiliser des bibliothèques qui fournissent le support d'itération asynchrone.

Les aides d'itérateurs fournissent une évaluation paresseuse de base intégrée au langage, tandis que RxJS offre des fonctionnalités avancées comme la gestion d'erreurs, la contre-pression, et des opérateurs complexes. Utilisez les aides d'itérateurs pour des transformations simples et RxJS pour la programmation réactive complexe.

Non, les méthodes de tableau restent le meilleur choix pour les petits jeux de données qui tiennent en mémoire et quand vous avez besoin d'un accès aléatoire ou de multiples itérations. Les aides d'itérateurs complètent les tableaux pour des cas d'usage spécifiques impliquant des données larges ou infinies.

Oui, étendez la classe Iterator ou utilisez Iterator.from() avec un objet personnalisé implémentant le protocole d'itérateur. Cela vous permet d'ajouter des transformations spécifiques au domaine tout en maintenant la compatibilité avec les aides intégrées.

Listen to your bugs 🧘, with OpenReplay

See how users use your app and resolve issues fast.
Loved by thousands of developers