Protocolo de Iteración: Crea Objetos Iterables Personalizados
Aprende a implementar el protocolo de iteración con Symbol.iterator para crear objetos iterables personalizados que funcionan con for...of, spread operator y destructuring.
TL;DR - Resumen rápido
- El protocolo de iteración permite que cualquier objeto sea iterable usando Symbol.iterator
- Un iterable es un objeto que implementa el método Symbol.iterator que retorna un iterador
- Un iterador es un objeto con un método next() que retorna {value, done}
- Los iterables funcionan con for...of, spread operator [...], destructuring y más
- Puedes crear iterables infinitos, secuencias personalizadas y estructuras de datos complejas
Introducción al Protocolo de Iteración
El protocolo de iteración es uno de los fundamentos más poderosos de JavaScript moderno. Permite que cualquier objeto defina cómo será iterado, habilitando el uso de sintaxis como for...of, el spread operator [...], y destructuring en estructuras personalizadas.
Antes de ES6, iterar sobre estructuras personalizadas era limitado y verboso. El protocolo de iteración estandariza cómo JavaScript itera sobre valores, permitiendo que crees tus propias colecciones y secuencias que se comporten como arrays, maps y sets nativos.
¿Por qué es importante?
El protocolo de iteración es la base de muchas características modernas de JavaScript: for...of, Array.from(), spread operator, destructuring, y más. Entenderlo te permite crear APIs más elegantes y estructuras de datos personalizadas que se integran perfectamente con el ecosistema de JavaScript.
El Protocolo de Iteración
El protocolo de iteración consta de dos partes: el protocolo iterable y el protocolo iterador. El protocolo iterable define qué objetos pueden ser iterados, mientras que el protocolo iterador define cómo se produce la secuencia de valores.
Iterable vs Iterador
Es crucial entender la diferencia entre un iterable y un iterador. Un iterable es un objeto que puede ser iterado, mientras que un iterador es el objeto que realiza la iteración y mantiene el estado actual.
Este ejemplo muestra la diferencia fundamental: el iterable (array) tiene el método Symbol.iterator, mientras que el iterador tiene el método next(). Cada vez que llamas a Symbol.iterator() en un iterable, obtienes un nuevo iterador con su propio estado.
Analogía práctica
Piensa en un iterable como una caja con libros y un iterador como alguien que saca los libros uno por uno. La caja (iterable) tiene muchas personas (iteradores) que pueden sacarle libros, pero cada persona tiene su propia posición en la secuencia.
Crear un Iterable Personalizado
Para hacer un objeto iterable, debes implementar el método Symbol.iterator. Este método debe retornar un objeto iterador que tenga un método next(). El método next() retorna un objeto con dos propiedades: value (el valor actual) y done (boolean que indica si la iteración terminó).
Este ejemplo crea un iterable simple que produce números del 1 al 5. El método Symbol.iterator retorna un objeto con un contador y un método next(). Cada llamada a next() incrementa el contador y retorna el valor actual con done: false hasta que se alcanza el límite.
Los iterables personalizados funcionan con todas las sintaxis de iteración de JavaScript:
- for...of: <code>for (const item of myIterable) { ... }</code>
- Spread operator: <code>[...myIterable]</code>
- Destructuring: <code>const [a, b, c] = myIterable</code>
- Array.from(): <code>Array.from(myIterable)</code>
- Yield*: <code>function* gen() { yield* myIterable }</code>
El Protocolo de Iterador
El protocolo de iterador define un contrato que el objeto iterador debe cumplir. El iterador debe tener un método next() que retorna un objeto con las propiedades value y done. Este método es llamado por JavaScript automáticamente cuando iteras.
El Método next()
El método next() es el corazón del protocolo de iterador. Cada llamada a next() avanza la iteración un paso y retorna el siguiente valor. Cuando no hay más valores, debe retornar { value: undefined, done: true }.
Este ejemplo muestra cómo funciona el método next() internamente. Cada llamada avanza el estado y retorna el siguiente valor. Es importante que el iterador mantenga su propio estado entre llamadas a next().
Advertencia: Iteradores son de un solo uso
Una vez que un iterador retorna done: true, no debe cambiar de estado. Llamar a next() nuevamente debe seguir retornando { value: undefined, done: true }. Si necesitas iterar nuevamente, debes crear un nuevo iterador llamando a Symbol.iterator() nuevamente.
Casos de Uso Prácticos
Los iterables personalizados son útiles en muchos escenarios reales. Puedes crear secuencias infinitas, rangos personalizados, estructuras de datos complejas, y más. Aquí veremos algunos casos prácticos.
Este ejemplo crea una función Range que genera un iterable de números en un rango específico. Es más flexible que usar un array porque no necesita almacenar todos los valores en memoria, lo cual es eficiente para rangos grandes.
Los iteradores infinitos son poderosos porque pueden generar valores bajo demanda sin límite. Este ejemplo crea un iterador de números Fibonacci infinito. Cuando se usa con for...of, debes incluir una condición de break para evitar un bucle infinito.
Lazy evaluation
Los iterables implementan lazy evaluation: los valores se calculan solo cuando se solicitan. Esto es especialmente útil para secuencias grandes o infinitas, ya que no necesitas calcular ni almacenar todos los valores por adelantado.
Errores Comunes
Al trabajar con el protocolo de iteración, hay varios errores comunes que debes evitar. El error más frecuente es cuando Symbol.iterator no retorna un objeto iterador válido con un método next(). También es común que next() retorne un valor primitivo en lugar de un objeto con las propiedades value y done.
El ejemplo muestra los tres errores principales: no retornar un iterador válido, retornar un objeto sin método next(), y que next() retorne valores incorrectos. Todos estos errores lanzan TypeError en tiempo de ejecución.
Error común: Olvidar done: true
Si tu iterador nunca retorna done: true, creará un bucle infinito cuando se use con for...of. Siempre asegúrate de que haya una condición de terminación clara y que next() eventualmente retorne done: true.
Resumen: Protocolo de Iteración
Conceptos principales:
- •El protocolo de iteración permite crear objetos iterables personalizados
- •Un iterable implementa Symbol.iterator que retorna un iterador
- •Un iterador tiene un método next() que retorna {value, done}
- •Los iterables funcionan con for...of, spread, destructuring y Array.from()
- •Los iteradores mantienen su propio estado de iteración
Mejores prácticas:
- •Siempre retorna un objeto iterador válido desde Symbol.iterator
- •El método next() siempre debe retornar {value, done}
- •Una vez done: true, next() debe seguir retornando done: true
- •Usa iterables para secuencias grandes o infinitas (lazy evaluation)
- •Crea un nuevo iterador para cada iteración, no reuses iteradores