Comprobando si algo es iterable

104

En los documentos de MDN: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/for...of

La for...ofconstrucción se describe para poder iterar sobre objetos "iterables". Pero, ¿existe una buena forma de decidir si un objeto es iterable?

Intenté encontrar propiedades comunes para matrices, iteradores y generadores, pero no pude hacerlo.

Aparte de hacer un for ... ofbloque de prueba y comprobar si hay errores de tipo, ¿hay una forma limpia de hacer esto?

Simonzack
fuente
Seguramente, como autor, ¿sabe si su objeto es iterable?
Andrewb
5
El objeto se pasa como argumento, no estoy seguro.
Simonzack
1
¿Por qué no probar el tipo de argumento?
James Bruckner
2
@ andrew-buchan, James Bruckner: La verificación de tipos puede funcionar, pero si lee los documentos de MDN, notará que dice "como una matriz". No sé qué significa esto exactamente, de ahí la pregunta.
simonzack
1
wiki.ecmascript.org/doku.php?id=harmony:iterators dice " Un objeto es iterable si tiene un iterator()método ". Sin embargo, dado que se trata de un borrador, solo un control podría depender de la implementación. ¿Qué entorno utilizas?
Bergi

Respuestas:

142

La forma correcta de verificar la iterabilidad es la siguiente:

function isIterable(obj) {
  // checks for null and undefined
  if (obj == null) {
    return false;
  }
  return typeof obj[Symbol.iterator] === 'function';
}

Por qué funciona esto (protocolo iterable en profundidad): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Ya que estamos hablando de ... de, supongo que estamos en la mentalidad de ES6.

Además, no se sorprenda de que esta función devuelva truesi objes una cadena, ya que las cadenas iteran sobre sus caracteres.

Tomas Kulich
fuente
14
o Symbol.iterator in Object(obj),.
14
Hay (al menos) una excepción al uso del operador 'in': cadena. La cadena es iterable (en términos de for..of) pero no puede usar 'in' en ella. Si no fuera por esto, preferiría usar 'in', definitivamente se ve mejor.
Tomas Kulich
¿No debería ser return typeof obj[Symbol.iterator] === 'function'? "Para ser iterable, un objeto debe implementar el método @@ iterator" - especifica el método
callum
No hay una semántica adecuada para que obj [Symbol.iterator] sea algo más que (undefined o) función. Si alguien pone, por ejemplo, String allí, es algo malo y, en mi opinión, es bueno si el código falla lo antes posible.
Tomas Kulich
¿ typeof Object(obj)[Symbol.iterator] === 'function'Funcionaría en todos los casos?
Craig Gidney
26

¿Por qué tan prolijo?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'
adio
fuente
57
Readability > Clevernesssiempre vuelve true.
jfmercer
27
Jaja, normalmente soy yo quien se queja del código ilegible, pero en realidad creo que esto es bastante legible. Como una oración en inglés: si el objeto no es nulo y la propiedad del iterador simbólico es una función, entonces es iterable. Si eso no es
absolutamente
4
En mi opinión, el punto de "legibilidad" es comprender lo que está sucediendo sin una lectura real
Dmitry Parzhitsky
2
@Alexander Mills Su mejora empeoró el código. 1. Como dijo @jfmercer Readability > Cleverness, acortar la variable objecta ono ayuda a nadie. 2. Una cadena vacía ''es iterable y, por lo tanto, debe regresar true.
adius
1
Entendido, pensé que había una razón por la que se estaba usando!= null
Alexander Mills
22

La solución más simple es en realidad esta:

function isIterable (value) {
  return Symbol.iterator in Object(value);
}

Objectenvolverá todo lo que no sea un objeto en uno, lo que permitirá al inoperador trabajar incluso si el valor original no es un Objeto. nully undefinedse convierten en objetos vacíos, por lo que no hay necesidad de detección de casos de borde, y las cadenas se envuelven en objetos String que son iterables.

dominó
fuente
1
Usaste el símbolo incorrecto. Es: Symbol.iterator.
Gil
@Gil ¡Tienes toda la razón, oops! Debería haber copiado y pegado el código probado en lugar de escribirlo directamente en una publicación.
Domino
La creación de un nuevo objeto con Objectes un desperdicio para este tipo de verificación.
Ruben Verborgh
No puedo decir que sé exactamente cómo Objectse implementa la función, pero solo crea un nuevo objeto si aún valueno es un objeto. Espero que la combinación de iny Object(...)sea ​​algo que los motores de navegador puedan optimizar fácilmente, a diferencia de, por ejemplo value !== undefined && value !== null && value[Symbol.iterator] && true. Además, es extremadamente legible, lo que me importa.
Domino
9

Como nota al margen, TENGA CUIDADO con la definición de iterable . Si viene de otros idiomas, esperaría que algo con lo que pueda iterar, por ejemplo, un forbucle sea iterable . Me temo que ese no es el caso aquí, donde iterable significa algo que implementa el protocolo de iteración .

Para aclarar las cosas, todos los ejemplos anteriores devuelven falseeste objeto {a: 1, b: 2}porque ese objeto no implementa el protocolo de iteración. Por lo tanto, no podrá iterar sobre él con un for...of PERO todavía puede hacerlo con un for...in.

Entonces, si desea evitar errores dolorosos, haga que su código sea más específico cambiando el nombre de su método como se muestra a continuación:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);
Francesco Casula
fuente
No tiene sentido. ¿Por qué crees que se rompería undefined?
adius
@adius Creo que asumí erróneamente que lo estaba haciendo object !== nullen su respuesta, pero lo está haciendo, object != nullpor lo que no está rompiendo undefineden ese caso específico. He actualizado mi respuesta en consecuencia.
Francesco Casula
OK veo. Por cierto: su código es incorrecto. hasIterationProtocol('')debe regresar true! ¿Qué tal si elimina su código y simplemente deja la sección de explicación iterable, que es lo único que agrega valor real / algo nuevo en su respuesta?
adius
1
Vuelve true, al eliminar parte de la respuesta anterior fusioné ambas funciones y me olvidé de la comparación estricta. Ahora devolví la respuesta original que estaba funcionando bien.
Francesco Casula
1
Nunca hubiera pensado en usar Object(...), buena captura. Pero en ese caso no se necesita la verificación nula.
Domino
2

Hoy en día, como ya se dijo, para probar si objes iterable simplemente haga

obj != null && typeof obj[Symbol.iterator] === 'function' 

Respuesta histórica (no más válida)

los for..of constructo es parte del borrador de especificación de idioma de la 6ª edición de ECMASCript. Entonces podría cambiar antes de la versión final.

En este borrador, los objetos iterables deben tener la funcióniterator como propiedad.

Puede verificar si un objeto es iterable como este:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}
Ortomala Lokni
fuente
7
La propiedad del iterador era una cosa temporal de Firefox. No es compatible con ES6. ver: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad
2

Para iteradores asíncronos , debe buscar el 'Symbol.asyncIterator' en lugar de 'Symbol.iterator':

async function* doSomething(i) {
    yield 1;
    yield 2;
}

let obj = doSomething();

console.log(typeof obj[Symbol.iterator] === 'function');      // false
console.log(typeof obj[Symbol.asyncIterator] === 'function'); // true
Kamarey
fuente
1

Si quisiera comprobar de hecho si una variable es un objeto ( {key: value}) o una matriz ( [value, value]), puede hacerlo:

const isArray = function (a) {
    return Array.isArray(a);
};

const isObject = function (o) {
    return o === Object(o) && !isArray(o) && typeof o !== 'function';
};

function isIterable(variable) {
    return isArray(variable) || isObject(variable);
}
Seglinglin
fuente