Javascript— Entendendo Iterators

Lucas Santos
Training Center
Published in
7 min readNov 1, 2017

Com certeza você já ouviu falar sobre iterators em outras linguagens como o C++ e o C#, mas aposto que você não sabia que o Javascript também era um adepto deste protocolo. Você sabe o que é um iterator?

Os protocolos Iterator e Iterable

No Javascript agora podemos usufruir de dois novos protocolos no ES6, os chamados iterators e suas contrapartes iterables. Mas o que são protocolos? Bem, pondo em palavras simples, um protocolo é algo que você pode pensar como uma convenção, um acordo entre varias partes. O que significa que se você segue este protocolo em uma linguagem, você obtém dele um efeito colateral.

Em Javascript, os iterables permitem que você define como um objeto vai se comportar quando eles são iterados. Por baixo dos panos, lá nos poços sombrios dos bits dos interpretadores Javascript temos um método muito importante chamado @@iterator . Este método implementa o protocolo iterable e pode ser acessado por nós, meros mortais, usando o objeto Symbol.iterator , que é um símbolo.

Um exemplo de um iterator

Ainda não vamos discutir símbolos aqui, mas antes de mais nada tenho que dizer que o @@iterator só é chamado uma vez quando o objeto precisa ser iterado. Por exemplo, no início de um loop do tipo for .. of , vamos ter que pedir para o @@iterator para que ele nos devolva um objeto iterator que vai ser usado para obter os valores do objeto que queremos iterar.

Complicado não é? Pense que o @@iterator é quem vai devolver para o laço o meio de passar por todos os itens de uma sequência. Se você ainda não entendeu o que é um objeto “iterável”, ele é qualquer tipo de coisa que você consegue realizar um laço sobre, por exemplo, um array, um objeto, uma sequencia de números, uma stream, ou qualquer coisa.

Como faço um iterator?

Abaixo vou colocar um snippet de código que mostra como podemos criar um iterator. A primeira coisa que você precisa notar é que estamos transformando nosso objeto em um objeto do tipo iterable só associando @@iterator (que é uma propriedade mística se formos pensar bem) através do Symbol.iterator .

Não podemos utilizar um Symbol como propriedade direta, então temos que colocá-lo entre colchetes, isso significa que vamos utilizar não o nome Symbol.iterator , mas o valor final que vai ser retornado após a avaliação do código da expressão Symbol.iterator . Isto é muito comum quando queremos trazer, por exemplo, um resultado de uma função para uma chave dos nossos objetos.

O objeto que é retornado pelo método associado ao [Symbol.iterator] precisa aderir ao protocolo iterator. Este protocolo é quem vai definir como podemos extrair valores de um objeto que é iterável, e nós, por outro lado, precisamos retornar algo que seja aderente ao protocolo iterator, que, consequentemente, é um @@iterator .

Segundo o protocolo, temos que ter uma coisa na nossa interface iterator. Um objeto com um método next , método este que não pode ter nenhum argumento e deve retornar um outro objeto com uma assinatura igual a esta: { done: boolean, value: any } .

  • done : indica quando a sequencia acabou, ou seja, quando o nosso iterador deve parar de iterar sobre o objeto. Esta propriedade deve ser um boolean retornando true quando terminamos de iterar e false caso contrário.
  • value : É o valor atual da sequencia, o item que está sendo iterado no momento.
Temos que aderir a umas regras se quisermos utilizar iterators (imagem daqui)

No exemplo abaixo, o iterador retorna um objeto que é uma lista finita de itens. Emitimos os valores destes itens até que não haja mais nenhum:

Pra iterar sobre o objeto propriamente dito a gente precisa usar um laço, vamos usar um for .. of , que também é algo novo no ES6, e é o responsável por acabar com a eterna guerra entre os laços e as collections que retornavam coisas que não faziam parte delas.

Além disso, podemos usar o for .. of para iterar sobre qualquer objeto que adere ao protocolo iterable. No ES6, os objetos que compõe essa lista são:

  • Arrays, qualquer tipo de arrays
  • Qualquer objeto que tenha um [Symbol.iterator] definido pelo usuário (como fizemos acima)
  • Generators
  • Resultados de nós do DOM que vem de um .querySelectorAll e derivados
  • Etc…

Se você quiser magicamente transformar qualquer iterable em um array, basta utilizar um spread operator, ou então o método Array.from(iterable) .

Recaptulando

Nosso objeto foo adere ao protocolo iterable porque ele associa um método ao [Symbol.iterator] isso nos diz que o objeto é um iterable e pode ser iterado. Tal método retorna um objeto que adere ao protocolo iterator. E este protocolo é chamado sempre que quisermos iniciar uma iteração sobre o objeto, este iterator que retornamos é usado para “puxar” os valors do nosso iterable chamado foo . Para iterar sobre os objetos que aderem ao protocolo iterable podemos usar for .. of , um spread operator ou então um Array.from(iterable) . Sendo que nos últimos casos o resultado final vai ser transformado em um Array e no primeiro ele vai ser obtido item a item.

Então temos um objeto, esse objeto tem um indice [Symbol.iterator] que é uma função, essa função retorna um objeto que contém um done , que é um boolean que nos diz quando a sequencia acabou, e um next que diz como podemos pegar o próximo item. Isso, muito brevemente é um iterator.

Mas o que isso tudo significa?

Em essência, o ponto mais importante disso tudo, tanto os protocolos de iteração quanto for .. of , Array.from e derivados é que eles provém um meio muito expressivo de iterarmos sobre collections e os chamados array-likes (objetos que provém ou são definidos a partir de arrays e possuem propriedades semelhantes) sem muito esforço, veja, podemos definir nosso iterador da forma que quisermos, isso abre uma gama de aplicações absurda.

Ter a habilidade de definir como qualquer objeto pode ser iterado permite que grandes libs como o lo-dash e outras, possam convergir sob um único teto, um único protocolo que a linguagem entende de forma nativa. Os iterables.

Um exemplo muito importante de como isso pode melhorar muito o nosso desenvolvimento é o próprio jQuery. Algumas pessoas reclamam bastante de que os objetos wrapper retornados de seletores como $('li') não são na verdade arrays, uma vez que não podemos iterar sobre eles dessa forma que apresentei.

Se os objetos retornados por ele fossem, de fato, iterables poderíamos fazer algo assim:

O objeto do jQuery não está ali por acaso, ele vai permitir que pudéssemos iterar mais fundo de forma mais simples:

Devagar por natureza

Iterators são chamados de lazy in nature. Isso é um jeito suntuoso de falar que eles só acessam um item por vez. Pode até ser uma sequencia infinita, como é em muitos casos. Como os iterators tem essa característica, ao invés de colocarmos um objeto jQuery em volta de uma lista que é infinita (o que criaria um problema de memória bastante sério), vamos adicionando este objeto a cada item, recriando ele sobre cada um dos itens iterados.

Como um iterador infinito se parece? Bom, podemos contar de 1 até o infinito de forma muito simples:

Isso vai contar infinitamente. Perceba que nunca chamamos um done , de forma que a sequencia é infinita, porque ela nunca é terminada diretamente. Tentar transformar esse tipo de iterable em um Array usando Array.from ou um [...foo] vai te dar um crash. Então temos que ser muito cuidadosos com esse tipo de sequencias, porque eles podem consumir todo o nosso processo do Node.

O modo correto de trabalhar com estes iteradores é com uma condição de escape que previne que o loop seja infinito. No exemplo abaixo podemos, por exemplo, usar um for .. of para contar até 10 e depois paramos o iterador:

O iterator não sabe realmente que a sequencia acabou ou não.

O problema da parada por XKCD

Isso é uma boa aplicação do problema de parada, onde Alan Turing mostrou que não era possível descobrir quando um programa deveria parar, ou continuar rodando eternamente, pelo menos não dentro do código, porque o programa em si não sabe que ele tem ou não que acabar, pelo menos não enquanto usarmos máquinas de Turing para escrever nossos códigos.

Conclusão

Iterators são complicados, mas com um pouco de base e estudo podemos entender tranquilamente. Vou deixar alguns links de referência para continuar seus estudos!

Não deixe de acompanhar mais do meu conteúdo no meu blog e se inscreva na newsletter para receber notícias semanais!

--

--

Lucas Santos
Training Center

Brazilian Programmer, caught between the black screen and rock n' roll 🤘 — Senior software Engineer