DMarlon Studio

Lección 7 · 7 de 20

Iteradores y generadores · JavaScript

Cree secuencias bajo demanda en JavaScript con function* y yield, y haga sus objetos recorribles con for...of mediante Symbol.iterator.

JavaScript · Iteradores y generadores en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función generadora se declara con function* y, en vez de devolver un valor y terminar, se pausa en cada yield. Llamar a invoiceNumbers("INV", 7) no ejecuta nada todavía: devuelve un generador. Cada next() reanuda el código hasta el siguiente yield y entrega un objeto { value, done }, como muestra la tercera línea de la salida.

Por eso un generador puede ser infinito sin bloquear el programa: el bucle for sin condición de invoiceNumbers solo avanza cuando alguien pide otro número. Es una forma cómoda de producir valores bajo demanda: numeraciones, páginas de una API o líneas de un fichero.

El protocolo de iteración es lo que usan for...of, el operador ... y la desestructuración: piden al objeto su método [Symbol.iterator](). Playlist lo define como generador (*[Symbol.iterator]()), así que sus canciones se recorren con for...of y se copian a un array con [...party].

countdown muestra lo que hay debajo, sin generadores: un iterador es simplemente un objeto con un método next() que devuelve { value, done }. Cuando done es true, el recorrido termina. Como [Symbol.iterator]() devuelve this, el objeto es a la vez iterable e iterador.

Código · main.js

// A generator pauses at each yield and resumes on the next call to next().function* invoiceNumbers(prefix, start) {  for (let n = start; ; n++) {    yield `${prefix}-${String(n).padStart(4, "0")}`;  }}const numbers = invoiceNumbers("INV", 7);console.log(numbers.next().value);console.log(numbers.next().value);console.log(numbers.next());// Any object with [Symbol.iterator] works with for...of and spread.class Playlist {  constructor(...songs) {    this.songs = songs;  }  *[Symbol.iterator]() {    for (const song of this.songs) yield song.toUpperCase();  }}const party = new Playlist("intro", "hit single", "encore");for (const song of party) console.log("Playing", song);console.log([...party].length, "songs in the playlist");// A hand-written iterator: an object whose next() returns { value, done }.function countdown(from) {  let current = from;  return {    [Symbol.iterator]() { return this; },    next: () => (current > 0 ? { value: current--, done: false } : { value: undefined, done: true }),  };}console.log("Launch in", [...countdown(3)].join(", "));

Salida · node main.js

INV-0007
INV-0008
{ value: 'INV-0009', done: false }
Playing INTRO
Playing HIT SINGLE
Playing ENCORE
3 songs in the playlist
Launch in 3, 2, 1

Para recordar

  • function* define un generador; yield entrega un valor y pausa.
  • Cada next() devuelve { value, done }.
  • Un generador puede ser infinito porque solo calcula lo que se le pide.
  • Un objeto con [Symbol.iterator]() funciona con for...of y con ....

Ejercicio

Escriba un generador take(iterable, n) que entregue solo los n primeros valores de otro iterable, y úselo para obtener cinco números de factura con [...take(invoiceNumbers("INV", 1), 5)].