Este artículo es una introducción en profundidad a los iterables y los iteradores en JavaScript. Mi principal motivación para escribirlo fue prepararme para aprender sobre generadores. De hecho, estaba planeando experimentar más tarde combinando generadores y anzuelos React. Si estás interesado, ¡sigue mi Twitter o YouTube !
En realidad, planeé comenzar con un artículo sobre generadores, pero pronto se hizo evidente que es difícil hablar de ellos sin una buena comprensión de los iterables y los iteradores. Nos centraremos en ellos ahora. Asumiré que no sabes nada sobre este tema, pero al mismo tiempo profundizaremos en él de manera significativa. Entonces si eres algo conozca los iterables y los iteradores, pero no se sienta cómodo usándolos, este artículo lo ayudará.
Introducción
Como notó, estamos discutiendo iterables e iteradores. Estos son conceptos interrelacionados, pero diferentes, por lo que al leer el artículo, preste atención a cuál se está discutiendo en un caso particular.
Comencemos con los objetos iterables. ¿Lo que es? Esto es algo que se puede iterar, por ejemplo:
for (let element of iterable) {
// do something with an element
}
Tenga en cuenta que aquí solo estamos viendo los bucles
for ... of
que se introdujeron en ES6. Y los bucles
for ... in
son una construcción más antigua a la que no nos referiremos en absoluto en este artículo.
Ahora podría estar pensando: "¡Está bien, esta variable iterable es solo una matriz!" Así es, las matrices son iterables. Pero ahora hay otras estructuras en JavaScript nativo que puede usar en un bucle
for ... of
. Es decir, además de las matrices, hay otros objetos iterables.
Por ejemplo, podemos iterar
Map
, introducido en ES6:
const ourMap = new Map();
ourMap.set(1, 'a');
ourMap.set(2, 'b');
ourMap.set(3, 'c');
for (let element of ourMap) {
console.log(element);
}
Este código mostrará:
[1, 'a']
[2, 'b']
[3, 'c']
Es decir, la variable
element
en cada etapa de iteración almacena una matriz de dos elementos. La primera es la clave, la segunda es el valor.
El hecho de que pudiéramos usar un bucle
for ... of
para iterar
Map
demuestra que
Map
son iterables. Nuevamente
for ... of
, solo se pueden usar objetos iterables en bucles . Es decir, si algo funciona con este bucle, entonces es un objeto iterable.
Es curioso que el constructor
Map
acepte opcionalmente iterables de pares clave-valor. Es decir, esta es una forma alternativa de construir el mismo
Map
:
const ourMap = new Map([
[1, 'a'],
[2, 'b'],
[3, 'c'],
]);
Y como
Map
es un iterable, podemos hacer copias de él muy fácilmente:
const copyOfOurMap = new Map(ourMap);
Ahora tenemos dos diferentes
Map
, aunque almacenan las mismas claves con los mismos valores.
Entonces vimos dos ejemplos de objetos iterables: matriz y ES6
Map
. Pero aún no sabemos cómo consiguieron la capacidad de ser iterables. La respuesta es simple: hay iteradores asociados con ellos . Tenga cuidado: los iteradores no son iterables .
¿Cómo se asocia un iterador con un objeto iterable? Un objeto simplemente iterable debe contener una función en su propiedad
Symbol.iterator
. Cuando se llama, la función debe devolver un iterador para este objeto.
Por ejemplo, puede recuperar un iterador de matriz:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
console.log(iterator);
Este código se envía a la consola
Object [Array Iterator] {}
. Ahora sabemos que la matriz tiene un iterador asociado, que es algún tipo de objeto.
¿Qué es un iterador?
Es simple. Un iterador es un objeto que contiene un método
next
. Cuando se llama a este método, debería devolver:
- siguiente valor en una secuencia de valores;
- información sobre si el iterador ha terminado de generar valores.
Probemos esto llamando a un método
next
en nuestro iterador de matriz:
const result = iterator.next();
console.log(result);
Veremos el objeto en la consola
{ value: 1, done: false }
. El primer elemento de la matriz que creamos es 1, y aquí apareció como un valor. También recibimos información de que el iterador aún no ha terminado, es decir, aún podemos llamar a la función
next
y obtener algunos valores. ¡Intentemos! Llamémoslo
next
dos veces más:
console.log(iterator.next());
console.log(iterator.next());
Recibido uno por uno
{ value: 2, done: false }
y
{ value: 3, done: false }
.
Solo hay tres elementos en nuestra matriz. ¿Qué pasa si lo vuelves a llamar
next
?
console.log(iterator.next());
Esta vez veremos
{ value: undefined, done: true }
. Esto indica que el iterador está completo. No tiene sentido volver a llamar
next
. Si hacemos esto, una y otra vez recibiremos un objeto
{ value: undefined, done: true }
.
done: true
significa dejar de iterar.
Ahora puedes entender lo que hace
for ... of
bajo el capó:
[Symbol.iterator]()
se llama al primer método para obtener el iterador;- el método
next
se llama cíclicamente en el iterador hasta que lo obtenemosdone: true
; - después de cada llamada
next
, la propiedad se usa en el cuerpo del ciclovalue
.
Escribamos todo esto en código:
const iterator = ourArray[Symbol.iterator]();
let result = iterator.next();
while (!result.done) {
const element = result.value;
// do some something with element
result = iterator.next();
}
Este código es equivalente a esto:
for (let element of ourArray) {
// do something with element
}
Puede verificar esto, por ejemplo, insertando en
console.log(element)
lugar de un comentario
// do something with element
.
Crea tu propio iterador
Ahora sabemos qué son los iterables y los iteradores. Surge la pregunta: "¿Puedo escribir mis propias instancias?"
¡Ciertamente!
Los iteradores no tienen nada de misterioso. Estos son solo objetos con un método
next
que se comportan de una manera especial. Ya hemos descubierto qué valores nativos en JS son iterables. No se mencionaron objetos entre ellos. De hecho, no se repiten de forma nativa. Considere un objeto como este:
const ourObject = {
1: 'a',
2: 'b',
3: 'c'
};
Si iteramos con él
for (let element of ourObject)
, obtenemos un error
object is not iterable
.
¡Escribamos nuestros propios iteradores haciendo que dicho objeto sea iterable!
Para hacer esto, debe parchear el prototipo
Object
con su propio método
[Symbol.iterator]()
. Dado que parchear el prototipo es una mala práctica, creemos nuestra propia clase ampliando
Object
:
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
}
El constructor de nuestra clase toma un objeto ordinario y copia sus propiedades en un objeto iterable (¡aunque todavía no es iterable!).
Creemos un objeto iterable:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
})
Para que una clase sea
IterableObject
verdaderamente iterable, necesitamos un método
[Symbol.iterator]()
. Vamos a agregarlo.
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
[Symbol.iterator]() {
}
}
¡Ahora puedes escribir un iterador real!
Ya sabemos que debe ser un objeto con un método
next
. Empecemos por esto.
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
return {
next() {}
}
}
}
Después de cada llamada,
next
debe devolver un objeto de vista
{ value, done }
. Hagámoslo con valores ficticios.
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
Dado un objeto iterable como este:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
})
generaremos pares clave-valor, similar a cómo lo hace la iteración ES6
Map
:
['1', 'a']
['2', 'b']
['3', 'c']
En nuestro iterador,
property
almacenaremos una matriz en el valor
[key, valueForThatKey]
. Tenga en cuenta que esta es nuestra propia solución en comparación con los pasos anteriores. Si quisiéramos escribir un iterador que devuelva solo claves o solo valores de propiedad, entonces podríamos hacerlo sin ningún problema. Decidimos devolver pares clave-valor ahora.
Necesitamos una matriz del tipo
[key, valueForThatKey]
. La forma más sencilla de conseguirlo es con el método
Object.entries
. Podemos usarlo justo antes de crear el objeto iterador en el método
[Symbol.iterator]()
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// we made an addition here
const entries = Object.entries(this);
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
El iterador devuelto en el método accederá a la variable gracias al cierre de JavaScript
entries
.
También necesitamos una variable de estado. Nos dirá qué par clave-valor debe devolverse en la próxima llamada
next
. Vamos a agregarlo:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
// we made an addition here
let index = 0;
return {
next() {
return {
value: undefined,
done: false
}
}
}
}
}
Tenga en cuenta que declaramos la variable
index
c
let
porque sabemos que planeamos actualizar su valor después de cada llamada
next
.
Ahora estamos listos para devolver el valor real en el método
next
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
return {
// we made a change here
value: entries[index],
done: false
}
}
}
}
}
Fue fácil. Solo usamos variables
entries
y
index
para acceder al par clave-valor correcto de la matriz
entries
.
Ahora tenemos que ocuparnos de la propiedad
done
, porque ahora siempre lo será
false
. Puede crear una variable más además de
entries
y
index
, y actualizarla después de cada llamada
next
. Pero hay una forma aún más sencilla. Comprobemos si
index
la matriz está fuera de los límites
entries
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
return {
value: entries[index],
// we made a change here
done: index >= entries.length
}
}
}
}
}
Nuestro iterador termina cuando la variable
index
es igual o mayor que su longitud
entries
. Por ejemplo, si y tiene una
entries
longitud de 3, entonces contiene valores en los índices 0, 1 y 2. Y cuando la variable
index
es igual o mayor que 3, significa que no quedan más valores. Terminamos.
Este código casi funciona. Solo queda una cosa por agregar.
La variable
index
comienza en 0, pero ... ¡no la actualizamos! No es tan simple. Necesitamos actualizar la variable después de haber regresado
{ value, done }
. Pero cuando lo devolvimos, el método
next
se detiene inmediatamente incluso si hay algún código después de la expresión
return
. Pero podemos crear un objeto
{ value, done }
, almacenarlo en una variable, actualizarlo
index
y solo entonces devolver el objeto:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
const result = {
value: entries[index],
done: index >= entries.length
};
index++;
return result;
}
}
}
}
Después de nuestros cambios, la clase se
IterableObject
ve así:
class IterableObject extends Object {
constructor(object) {
super();
Object.assign(this, object);
}
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = 0;
return {
next() {
const result = {
value: entries[index],
done: index >= entries.length
};
index++;
return result;
}
}
}
}
El código funciona muy bien, pero se volvió bastante confuso. Esto se debe a que muestra una forma más inteligente pero menos obvia de actualizar
index
después de la creación del objeto
result
. ¡Podemos simplemente inicializar
index
a -1! Y aunque se actualiza antes de devolver el objeto desde
next
, todo funcionará bien, porque la primera actualización reemplazará -1 con 0.
Así que hagámoslo:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
const entries = Object.entries(this);
let index = -1;
return {
next() {
index++;
return {
value: entries[index],
done: index >= entries.length
}
}
}
}
}
Como puede ver, ahora no necesitamos hacer malabarismos con el orden de creación
result
y actualización de objetos
index
. Durante la segunda llamada, se
index
actualizará a 1, y devolveremos un resultado diferente, y así sucesivamente. Todo funciona como queríamos y el código parece mucho más simple.
Pero, ¿cómo comprobamos la corrección del trabajo? Puede ejecutar manualmente un método
[Symbol.iterator]()
para crear una instancia de un iterador y luego verificar directamente los resultados de las llamadas
next
. ¡Pero puedes hacerlo mucho más fácil! Se dijo anteriormente que cualquier objeto iterable se puede insertar en un bucle
for ... of
. Hagamos precisamente eso, registrando los valores devueltos por nuestro objeto iterable en el camino:
const iterableObject = new IterableObject({
1: 'a',
2: 'b',
3: 'c'
});
for (let element of iterableObject) {
console.log(element);
}
¡Trabajos! Esto es lo que se muestra en la consola:
[ '1', 'a' ]
[ '2', 'b' ]
[ '3', 'c' ]
¡Frio! Comenzamos con un objeto que no se podía usar en bucles
for ... of
, porque no contienen de forma nativa iteradores integrados. Pero creamos el nuestro
IterableObject
, que tiene un iterador autoescrito asociado.
Espero que ahora pueda ver el potencial de los iterables y los iteradores. Es un mecanismo que le permite crear sus propias estructuras de datos para trabajar con funciones JS como bucles
for ... of
, ¡y funcionan como estructuras nativas! Esta es una característica muy útil que puede simplificar enormemente su código en ciertas situaciones, especialmente si planea iterar sus estructuras de datos con frecuencia.
Además, podemos personalizar qué deberían devolver exactamente estas iteraciones. Nuestro iterador ahora devuelve pares clave-valor. ¿Y si solo queremos valores? Fácil, simplemente reescribe el iterador:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// changed `entries` to `values`
const values = Object.values(this);
let index = -1;
return {
next() {
index++;
return {
// changed `entries` to `values`
value: values[index],
// changed `entries` to `values`
done: index >= values.length
}
}
}
}
}
¡Y eso es! Si ahora iniciamos el bucle
for ... of
, veremos en la consola:
a b c
Devolvimos solo los valores de los objetos. Todo esto demuestra la flexibilidad de los iteradores escritos por ellos mismos. Puedes hacer que te devuelvan lo que quieras.
Iteradores como ... objetos iterables
Es muy común que las personas confundan iteradores e iterables. Esto es un error y he tratado de separar cuidadosamente los dos. Sospecho que conozco la razón por la que la gente los confunde con tanta frecuencia.
Resulta que los iteradores ... ¡a veces son iterables!
¿Qué significa esto? Como recordará, un iterable es el objeto con el que está asociado un iterador. ¡Cada iterador de JavaScript nativo tiene un método
[Symbol.iterator]()
que devuelve otro iterador! Esto hace que el primer iterador sea un objeto iterable.
Puede verificar esto tomando un iterador devuelto de una matriz e invocándolo
[Symbol.iterator]()
:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
const secondIterator = iterator[Symbol.iterator]();
console.log(secondIterator);
Después de ejecutar este código, verá
Object [Array Iterator] {}
. Es decir, un iterador no solo contiene otro iterador asociado a él, también es una matriz.
Si compara ambos iteradores con using,
===,
resulta que son exactamente iguales:
const iterator = ourArray[Symbol.iterator]();
const secondIterator = iterator[Symbol.iterator]();
// logs `true`
console.log(iterator === secondIterator);
Al principio, puede resultarle extraño el comportamiento de un iterador que es su propio iterador. Pero esta es una característica muy útil. No puede colocar un iterador desnudo en un bucle
for ... of
, solo acepta un objeto iterable: un objeto con un método
[Symbol.iterator]()
.
Sin embargo, la situación en la que un iterador es su propio iterador (y, por lo tanto, un objeto iterable) oculta el problema. Dado que los iteradores JS nativos contienen métodos
[Symbol.iterator]()
, puede pasarlos directamente a bucles sin dudarlo
for ... of
.
Como resultado, este fragmento:
const ourArray = [1, 2, 3];
for (let element of ourArray) {
console.log(element);
}
y éste:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
trabajar sin problemas y hacer lo mismo. Pero, ¿por qué alguien usaría iteradores como este en bucles directamente
for ... of
? A veces es simplemente inevitable.
En primer lugar, es posible que deba crear un iterador sin pertenecer a ningún iterable. Veremos este ejemplo a continuación, y no es raro. A veces simplemente no necesitamos el iterable en sí.
Y sería muy incómodo si tener un iterador simple significara que no puede usarlo
for ... of
. Por supuesto, puedes hacer esto manualmente usando un método
next
y, por ejemplo, un bucle
while
, pero hemos visto que esto requiere escribir mucho código y repetitivo.
La solución es simple: si desea evitar el código repetitivo y usar un iterador en un bucle
for ... of
, debe convertir el iterador en un objeto iterable.
Por otro lado, también obtenemos iteradores con bastante frecuencia de métodos distintos a
[Symbol.iterator]()
. Por ejemplo, ES6
Map
contiene métodos
entries
,
values
y
keys
. Todos devuelven iteradores.
Si los iteradores JS nativos no fueran también objetos iterables, no podría usar estos métodos directamente en bucles
for ... of
, como este:
for (let element of map.entries()) {
console.log(element);
}
for (let element of map.values()) {
console.log(element);
}
for (let element of map.keys()) {
console.log(element);
}
Este código funciona porque los iteradores devueltos por los métodos también son objetos iterables. De lo contrario, tendría que, por ejemplo, envolver el resultado de la llamada
map.entries()
en algún objeto iterable estúpido. Afortunadamente, no es necesario que hagamos esto.
Se considera una buena práctica crear sus propios objetos iterables. Especialmente si se devuelven de métodos distintos a
[Symbol.iterator]()
. Es fácil convertir un iterador en un objeto iterable. Déjame mostrarte un ejemplo de un iterador
IterableObject
:
class IterableObject extends Object {
// same as before
[Symbol.iterator]() {
// same as before
return {
next() {
// same as before
},
[Symbol.iterator]() {
return this;
}
}
}
}
Hemos creado un método
[Symbol.iterator]()
debajo del método
next
. Hizo de este iterador su propio iterador simplemente regresando
this
, lo que significa que regresa a sí mismo. Arriba, ya hemos visto cómo se comporta un iterador de matriz. Esto es suficiente para que nuestro iterador funcione en bucles
for ... of
incluso directamente.
Estado del iterador
Ahora debería ser obvio que cada iterador tiene un estado asociado. Por ejemplo, en un iterador,
IterableObject
almacenamos un estado, una variable
index
, como cierre. Y lo actualizamos después de cada paso de iteración.
¿Qué sucede después de que se completa el proceso de iteración? El iterador se vuelve inútil y puede (¡debería!) Eliminarlo. Puede ver que esto está sucediendo incluso con el ejemplo de objetos JS nativos. Tomemos un iterador de matriz e intentemos ejecutarlo dos veces en un bucle
for ... of
.
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
for (let element of iterator) {
console.log(element);
}
Se podría esperar que la consola para mostrar los números en dos ocasiones
1
,
2
y
3
. Pero el resultado será así:
1
2
3
¿Por qué?
Llamemos manualmente
next
después de que termine el ciclo:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
console.log(iterator.next());
El último registro se envía a la consola
{ value: undefined, done: true }
.
Eso es. Una vez finalizado el ciclo, el iterador pasa al estado "hecho". Ahora siempre devolverá un objeto
{ value: undefined, done: true }
.
¿Hay alguna forma de "restablecer" el estado del iterador para que se pueda usar por segunda vez
for ... of
? En algunos casos, es posible, pero no tiene sentido. Por tanto,
[Symbol.iterator]
es un método, no solo una propiedad. Puede volver a llamar al método y obtener otro iterador:
const ourArray = [1, 2, 3];
const iterator = ourArray[Symbol.iterator]();
for (let element of iterator) {
console.log(element);
}
const secondIterator = ourArray[Symbol.iterator]();
for (let element of secondIterator) {
console.log(element);
}
Ahora todo funciona como se esperaba. Veamos por qué funcionan varios bucles hacia adelante a través de la matriz:
const ourArray = [1, 2, 3];
for (let element of ourArray) {
console.log(element);
}
for (let element of ourArray) {
console.log(element);
}
¡Todos los bucles
for ... of
usan iteradores diferentes ! Una vez que el iterador y el bucle han terminado, este iterador ya no se usa.
Iteradores y matrices
Dado que usamos iteradores (aunque indirectamente) en bucles
for ... of
, pueden verse engañosamente como matrices. Pero hay dos diferencias importantes. Iterator y Array utilizan los conceptos de valores codiciosos y vagos. Cuando crea una matriz, en un momento dado tiene una cierta longitud y sus valores ya están inicializados. Por supuesto, puede crear una matriz sin ningún valor, pero ese no es el caso. Mi punto es que no es posible crear una matriz que inicialice sus valores solo después de acceder a ellos escribiendo
array[someIndex]
. Podría ser posible solucionar esto con un proxy o algún otro truco, pero por defecto, las matrices JavaScript no se comportan de esa manera.
Y cuando dicen que una matriz tiene una longitud, quieren decir que esta longitud es finita. No hay matrices infinitas en JavaScript.
Estas dos cualidades indican que las matrices son codiciosas .
Y los iteradores son vagos .
Para mostrar esto, crearemos dos de nuestros iteradores: el primero será infinito, a diferencia de los arreglos finitos, y el segundo inicializará sus valores solo cuando los solicite el usuario del iterador.
Comencemos con un iterador infinito. Suena intimidante, pero es muy sencillo de crear: el iterador comienza en 0 y devuelve el siguiente número de la secuencia en cada paso. Siempre.
const counterIterator = {
integer: -1,
next() {
this.integer++;
return { value: this.integer, done: false };
},
[Symbol.iterator]() {
return this;
}
}
¡Y eso es! Comenzamos con una propiedad de
integer
-1. En cada llamada,
next
lo incrementamos en 1 y lo devolvemos en el objeto como
value
. Tenga en cuenta que se utilizó el truco mencionado de nuevo: empezamos a -1 para devolver 0 la primera vez.
También echar un vistazo a la propiedad
done
. Se tendrá siempre ser falsa. ¡Este iterador no termina!
Además, hicimos que el iterador sea iterable dándole una implementación simple
[Symbol.iterator]()
.
Una última cosa: este es el caso que mencioné anteriormente: creamos un iterador, pero no necesita un padre iterable para funcionar.
Ahora probemos este iterador en un bucle
for ... of
. Solo debe recordar detener el bucle en algún momento, de lo contrario, el código se ejecutará para siempre.
for (let element of counterIterator) {
if (element > 5) {
break;
}
console.log(element);
}
Después del lanzamiento, veremos en la consola:
0
1
2
3
4
5
De hecho, hemos creado un iterador infinito que devuelve tantos números como desee. ¡Y fue muy fácil hacerlo!
Ahora escribamos un iterador que no cree valores hasta que se soliciten.
Bueno ... ¡ya lo hemos hecho!
¿Ha notado que
counterIterator
solo se almacena un número de propiedad en un momento dado
integer
? Este es el último número devuelto en la llamada
next
. Y esta es la misma pereza. Un iterador puede potencialmente devolver cualquier número (más precisamente, un entero positivo). Pero solo los crea cuando son necesarios: cuando se llama al método
next
.
Esto puede parecer bastante engañoso. Después de todo, los números se crean rápidamente y no ocupan mucho espacio en la memoria. Pero si está trabajando con objetos muy grandes que ocupan mucha memoria, a veces reemplazar matrices con iteradores puede ser muy útil, acelerando el programa y ahorrando memoria.
Cuanto más grande sea el objeto (o más tarde en crearse), mayor será el beneficio.
Otras formas de utilizar iteradores
Hasta ahora, solo hemos consumido iteradores en un bucle
for ... of
o manualmente usando
next
. Pero estas no son las únicas formas.
Ya hemos visto que el constructor
Map
toma iterables como argumento. También puede
Array.from
convertir fácilmente un iterable en una matriz utilizando el método . ¡Pero ten cuidado! Como dije, la pereza del iterador a veces puede ser una gran ventaja. La conversión a una matriz elimina la pereza. Todos los valores devueltos por el iterador se inicializan inmediatamente y luego se colocan en una matriz. Esto significa que si intentamos convertir infinito
counterIterator
en una matriz, conducirá al desastre.
Array.from
se ejecutará para siempre sin devolver un resultado. Entonces, antes de convertir un iterable / iterador en una matriz, debe asegurarse de que la operación sea segura.
Curiosamente, los iterables también funcionan bien con el operador de propagación
(...
). Recuerde que esto funciona de la misma
Array.from
manera cuando todos los valores del iterador se generan a la vez. Por ejemplo, puede crear su propia versión utilizando el operador de propagación
Array.from
. Simplemente aplique el operador al iterable y luego coloque los valores en una matriz:
const arrayFromIterator = [...iterable];
También puede obtener todos los valores del iterable y aplicarlos a la función:
someFunction(...iterable);
Conclusión
Espero que ahora comprenda el título del artículo Objetos e iteradores iterables. Aprendimos qué son, en qué se diferencian, cómo usarlos y cómo crear nuestras propias instancias. Ahora estamos completamente preparados para trabajar con generadores. Si está familiarizado con los iteradores, pasar al siguiente tema debería ser sencillo.