Iterator
Un iterador es un objeto que permite recorrer una colección de valores de forma secuencial, mediante la llamada al método next(). Este método devolvera un objeto con las propiedades: value (valor actual) y done (booleano que indica si ha terminado de recorrer la secuencia).
Se puede crear un iterador a partir de un array utilizando el método values().
const a = [23, 45, 67, 89, 12];
const iterador = a.values();
console.log(iterador.next()); // { value: 23, done: false }
console.log(iterador.next()); // { value: 45, done: false }
console.log(iterador.next()); // { value: 67, done: false }
console.log(iterador.next()); // { value: 89, done: false }
console.log(iterador.next()); // { value: 12, done: false }
console.log(iterador.next()); // { value: undefined, done: true }
// Otra forma de crear un iterador
const iterador2 = a[Symbol.iterator]();
console.log(iterador2.next()); // { value: 23, done: false }
// ... etc
También se puede utilizar el método Iterator.from(colección) para crear un iterador que recorra cualquier tipo de colección, incluyendo cadenas de texto.
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log(iterador.next()); // { value: 23, done: false }
// ... hasta que se acaben los elementos
const cadena = "Hola";
const iteradorCadena = Iterator.from(cadena);
console.log(iteradorCadena.next()); // { value: 'H', done: false }
// ... hasta que se acaben los caracteres
Se pueden recorrer los elementos de un iterador utilizando un bucle for..of.
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
for(const n of iterador){
console.log(n);
}
// Imprime los valores: 23 45 67 89 12
También se pueden aplicar operadores como spread (...) a los iteradores.
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log([...iterador, 100, 200, 300]); // [23, 45, 67, 89, 12, 100, 200, 300]
Importante: Los iteradores solo se pueden recorrer hacia delante, por lo que los valores ya leídos no pueden ser accedidos otra vez por el iterador. Por ejemplo, si recorremos todos lo valores del iterador y después lo transformamos a array, este retornará un array vacío.
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log(iterador.next()); // {value: 23, done: false}
console.log(iterador.next()); // {value: 45, done: false}
// El operador spread internamente recorre el iterador hasta el final (Desde la última posición)
console.log([...iterador]); // [67, 89, 12] -> El resto de valores
console.log([...iterador]); // [] -> Ya no quedan valores
Operaciones con iteradores
En la versión ES2025 de JavaScript se han introducido nuevos métodos para los iteradores (Iterator Helpers) que permiten hacer operaciones intermedias con los datos como filter, map, reduce, etc.
A diferencia de los métodos equivalentes en los arrays de JavaScript, los iteradores se comportan de manera más eficiente (similar a la colección Stream de Java), ya que no se generan arrays intermedios en cada operación, sino que se aplica cada operación de forma individual a cada elemento cuando se accede a su valor.
A continuación vamos a ver los nuevos métodos para iteradores con ejemplos de su funcionamiento.
toArray -> Devuelve los elementos restantes del iterador en una array.
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log(iterador.next()); // {value: 23, done: false}
console.log(iterador.next()); // {value: 45, done: false}
const restantes = iterador.toArray();
console.log(restantes); // [67, 89, 12]
forEach -> Recorre los elementos restantes aplicándoles la función correspondiente.
iterador.forEach(e => console.log(e));
find -> Devuelve el primer elemento, de entre los restantes, que cumple con la función predicate que recibe, o undefined.
console.log(iterador.find(e => e % 2 === 0)); // 12
every y some -> Devuelven un valor booleano indicando si todos o alguno (respectivamente) de los elementos restantes cumplen la condición del predicado.
console.log(iterador.every(e => e % 2 === 0)); // false
console.log(iterador.some(e => e % 2 === 0)); // true
drop -> Se salta (elimina) los n primeros elementos
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log(iterador.drop(3).toArray()); // [89, 12]
take -> Se queda con los n primeros elementos descartando el resto
const a = [23, 45, 67, 89, 12];
const iterador = Iterator.from(a);
console.log(iterador.take(3).toArray()); // [23, 45, 67]
filter -> devuelve otro iterador que recorre los elementos que cumplan con la condición del predicado.
console.log(iterador.filter((n) => n % 2 === 0).toArray()); // [12]
map -> Devuelve otro iterador que transforma los valores a partir de una función.
console.log(iterador.map((n) => n * 2).toArray()); // [46, 90, 134, 178, 24]
reduce -> Devuelve un valor final calculado en base a los elementos del iterador.
console.log(iterador.reduce((total, n) => total + n)); // 236
flatMap -> Similar a map, pero si devolvemos en la función una colección iterable, extrae sus elementos.
const a = ["hola", "adiós", "casa", "coche"];
const iterador = Iterator.from(a);
console.log(iterador.flatMap((s) => [...s]).toArray());
// ['h', 'o', 'l', 'a', 'a', 'd', 'i', 'ó', 's', 'c', 'a', 's', 'a', 'c', 'o', 'c', 'h', 'e']
Como es lógico, se pueden combinar varios de estos métodos, teniendo en cuenta que algunos de ellos devuelven otro iterador. Hasta que no se recorren los elementos no se aplican las funciones de transformación, filtrado, etc. Al contrario que ocurre con los métodos similares de arrays donde cada llamada implica la generación de un nuevo array intermedio en memoria.
const a = ["hola", "adiós", "casa", "coche"];
const iterador = Iterator.from(a);
console.log(
iterador
.flatMap((s) => [...s])
.filter((l) => /[aeiouáéíóú]/.test(l))
.map((l) => l.toUpperCase())
.take(5)
.toArray()
);
// ['O', 'A', 'A', 'I', 'Ó']