Verificar se algo é iterável

104

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

A for...ofconstrução é descrita para ser capaz de iterar sobre objetos "iteráveis". Mas existe uma boa maneira de decidir se um objeto é iterável?

Tentei encontrar propriedades comuns para arrays, iteradores e geradores, mas não consegui.

Além de fazer um for ... ofbloco try e verificar erros de tipo, existe uma maneira limpa de fazer isso?

simonzack
fonte
Certamente, como autor, você sabe se seu objeto é iterável?
andrewb de
5
O objeto é passado como um argumento, não tenho certeza.
simonzack
1
Por que não testar typeof do argumento?
James Bruckner
2
@andrew-buchan, James Bruckner: Verificar os tipos pode funcionar, mas se você ler os documentos MDN, notará que diz "tipo array". Não sei o que isso significa exatamente, daí a pergunta.
simonzack
1
wiki.ecmascript.org/doku.php?id=harmony:iterators afirma " Um objeto é iterável se tiver um iterator()método. ". No entanto, como este é um rascunho, apenas um cheque pode ser dependente da implementação. Qual ambiente você usa?
Bergi,

Respostas:

142

A maneira adequada de verificar a iterabilidade é a seguinte:

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

Por que isso funciona (protocolo iterável em profundidade): https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Iteration_protocols

Já que estamos falando sobre for..of, presumo, estamos na mentalidade ES6.

Além disso, não se surpreenda que esta função retorne truese objfor uma string, pois as strings iteram sobre seus caracteres.

Tomas Kulich
fonte
14
ou Symbol.iterator in Object(obj),.
14
Há (pelo menos) uma exceção ao uso do operador 'in': string. String é iterável (em termos de for..of), mas você não pode usar 'in' nela. Se não fosse por isso, eu preferiria usar 'in' para ficar definitivamente melhor.
Tomas Kulich
não deveria ser return typeof obj[Symbol.iterator] === 'function'? "Para ser iterável, um objeto deve implementar o método @@ iterador" - especifica o método
callum
Não há semântica apropriada para obj [Symbol.iterator] sendo outra coisa senão (indefinida ou) função. Se alguém colocar por exemplo String lá, é uma coisa ruim e IMO é bom se o código falhar o mais rápido possível.
Tomas Kulich
Funcionaria typeof Object(obj)[Symbol.iterator] === 'function'em todos os casos?
Craig Gidney
26

Por que tão prolixo?

const isIterable = object =>
  object != null && typeof object[Symbol.iterator] === 'function'
adius
fonte
57
Readability > Clevernesssempre retorna true.
jfmercer
27
Haha, normalmente sou eu que reclamo do código ilegível, mas na verdade acho que isso é bastante legível. Como uma frase em inglês: se o objeto não for nulo e a propriedade do iterador simbólico for uma função, então ela é iterável. Se isso não for muito simples, não sei o que é ...
adius
4
imo, o ponto de "legibilidade" é entender o que está acontecendo sem a leitura real
Dmitry Parzhitsky
2
@Alexander Mills Sua melhoria tornou o código pior. 1. Como disse @jfmercer Readability > Cleverness, encurtar a variável objectpara não oajuda ninguém. 2. Uma string vazia ''é iterável e, portanto, deve retornar true.
adius
1
Entendi, descobri que havia um motivo para ele estar usando!= null
Alexander Mills
22

A solução mais simples é realmente esta:

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

Objectirá envolver qualquer coisa que não seja um objeto em um, permitindo que o inoperador trabalhe mesmo se o valor original não for um objeto. nulle undefinedsão transformados em objetos vazios, portanto, não há necessidade de detecção de casos extremos, e as strings são envolvidas em objetos String que são iteráveis.

Dominó
fonte
1
Você usou o símbolo errado. É: Symbol.iterator.
Gil
@Gil Você está absolutamente certo, opa! Eu deveria ter copiado e colado o código testado em vez de digitar diretamente em uma postagem.
Domino
A criação de um novo objeto com Objecté um desperdício para este tipo de cheque.
Ruben Verborgh
Não posso dizer que sei exatamente como a Objectfunção é implementada, mas ela só cria um novo objeto se valueainda não for um objeto. Eu esperaria que a combinação de ine Object(...)fosse algo que os mecanismos de navegação podem otimizar facilmente, ao contrário de dizer value !== undefined && value !== null && value[Symbol.iterator] && true. Além disso, é extremamente legível, o que me interessa.
Domino
9

Como nota lateral, CUIDADO com a definição de iterável . Se você estiver vindo de outras linguagens, poderá esperar que algo que possa iterar com, digamos, um forloop seja iterável . Infelizmente, esse não é o caso aqui, onde iterável significa algo que implementa o protocolo de iteração .

Para tornar as coisas mais claras, todos os exemplos acima retornam falsesobre esse objeto {a: 1, b: 2}porque esse objeto não implementa o protocolo de iteração. Portanto, você não será capaz de iterar sobre ele com um, for...of MAS ainda pode com um for...in.

Portanto, se você deseja evitar erros dolorosos, torne seu código mais específico renomeando seu método conforme mostrado abaixo:

/**
 * @param variable
 * @returns {boolean}
 */
const hasIterationProtocol = variable =>
    variable !== null && Symbol.iterator in Object(variable);
Francesco Casula
fonte
Você não está fazendo sentido. Por que você acha que iria quebrar undefined?
adius
@adius Acho que assumi erroneamente que você estava fazendo isso object !== nullem sua resposta, mas você está fazendo object != nullisso, não há rompimento undefinednesse caso específico. Eu atualizei minha resposta de acordo.
Francesco Casula
OK eu vejo. Btw: Seu código está incorreto. hasIterationProtocol('')deve retornar true! Que tal você remover seu código e apenas sair da seção de explicação iterável, que é a única coisa que agrega valor real / algo novo em sua resposta.
adius
1
Ele retorna true, removendo parte da resposta antiga, eu mesclei as funções e esqueci a comparação estrita. Agora coloquei de volta a resposta original, que estava funcionando bem.
Francesco Casula
1
Eu nunca teria pensado em usar Object(...), boa pegada. Mas, nesse caso, a verificação de nulo não é necessária.
Domino
2

Hoje em dia, como já foi dito, para testar se objé iterável basta fazer

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

Resposta histórica (não mais válida)

O for..ofconstructo é parte do ECMASCript 6ª edição do Language Specification Draft. Portanto, isso pode mudar antes da versão final.

Neste rascunho, os objetos iteráveis devem ter a função iteratorcomo uma propriedade.

Você pode verificar se um objeto é iterável assim:

function isIterable(obj){
   if(obj === undefined || obj === null){
      return false;
   }
   return obj.iterator !== undefined;
}
Ortomala Lokni
fonte
7
A propriedade iterator era uma coisa temporária do Firefox. Não é compatível com ES6. consulte: developer.mozilla.org/en/docs/Web/JavaScript/Reference/…
Amir Arad
2

Para iteradores assíncronos, você deve verificar o 'Symbol.asyncIterator' em vez 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
fonte
1

Se você quiser verificar de fato se uma variável é um objeto ( {key: value}) ou um array ( [value, value]), você pode fazer isso:

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
fonte