Continuando com nossa série sobre o uso de JavaScript de forma assíncrona, não podemos deixar de passar pelos Generators.

É muito comum precisarmos manipular itens de uma lista e, para tal, o JavaScript evoluiu e passou a nos oferecer diversos métodos como map e filter em Arrays, e construtores na própria linguagem como for…of e for…in.

Justamente com esta evolução toda, ES6 nos trouxe os generators.

Conceito

Generators seguem um conceito onde você pode “continuar” de onde parou anteriormente, até completar uma determinada tarefa.

Por isso, acostume-se com a ideia de interromper o fluxo de sua função antes de terminar a tarefa, mas lembrando que a “situação” atual da função será retomada na próxima execução para continuar de onde havia parado.

Para auxiliar, temos os iterators symbol, o iterator, a spark e o novo token yield. Todos descritos abaixo 🙂

Iterator Symbol

Uma novidade no ES6 foi, justamente, a adição de Symbols. Eles nos oferecem acesso a funcionalidades da linguagem em um nível nunca antes visto.

O símbolo iterador é representado por @@iterator e pode ser acessado por meio da constante Symbol.iterator.

O mais legal é que podemos usá-lo até mesmo em nossos objetos nativos:


let arr = [1, 2, 3, 4, 5, 6, 7, 8, 9]; let it = arr[Symbol.iterator]();
console.log(it.next().value); // 1
console.log(it.next().value); // 2
console.log(it.next().value); // 3
console.log(it.next().value); // 4

Ao chamarmos arr[Symbol.iterator](), recebemos o iterador dessa nossa Array. Hora de nos aprofundarmos mais nisso…

Iterator

Um iterator (iterador) é um objeto que oferece a funcionalidade de navegar entre os itens de uma lista ou estrutura – um a um – em sequência. Este objeto oferece o método next, o qual retornará o próximo item da lista no seguinte formato:

{
  value: Mixed,
  done: Boolean
}

Onde value corresponde ao valor do item atual, e done, é um booleano que será true quando não houverem mais itens a serem iterados. Isso significa que, quando done for true, o value em value será undefined. Usaremos os generators a seguir para construirmos uns exemplos úteis com iterators.

Spark *

Para tornarmos uma função no JavaScript em um generator, usamos o token * junto ao token function.

function* myGen () { // ... }

O nome deste * (asterisco), que em inglês seria chamado de star é, na verdade, chamado de spark (“fagulha” ou “faísca”).

Isso quer dizer que – toda vez que executarmos tal função – ela nos retornará, na realidade, um novo generator.

yield

Acrescentamos um novo token, o yield. Yield remete-se à colheita no campo. Imagine que você precise fazer a colheita, mas é impossível finalizar toda a tarefa de uma só vez, você precisará voltar no dia seguinte para continuar de onde parou.

function* gen() { 
yield "A";
yield "B"; 
yield "C"; 
} 
var g = gen(); // "Generator { }" 

E assim, podemos usar nosso generator como um iterator.

    console.log(gen.next().value); // A 
    console.log(g.next().value); // B 
    console.log(g.next().value); // C 
    console.log(g.next().value); // undefined
    console.log(g.next().done); // true 

O interessante é que iterables seguem o mesmo padrão, portanto, se usarmos o Symbol.iterator podemos ainda ir mais longe:

    
var myList = {}; // aqui, definimos COMO nosso objeto itera 
    myList[Symbol.iterator] = function* () { 
        yield "A"; 
        yield "B"; 
        yield "C"; 
    };

for(item of myList){ 
    console.log(item); 
} // ["A", "B", "C"]

Outro uso seria “explodindo” a nossa lista:

[...myList]; // ["A", "B", "C"]

Podemos também usar o yield para gerarmos “infinitos” valores dinamicamente utilizando um “loop eterno”:

// nossa constante será o próprio generator 
const IDGenerator = (function* (){ 
    var i =1; 
    while (true) { 
        yield i++; 
    } 
})();

// e agora teremos quantas IDs únicas precisarmos
IDGenerator.next().value; // 1
IDGenerator.next().value; // 2
IDGenerator.next().value; // 3
IDGenerator.next().value; // 4
IDGenerator.next().value; // 5

Notem que este iterador nunca será concluído, done, nunca será true.

Yield*

Temos também como usar o yield* para nos referenciarmos a outro generator. Neste caso, este yield só será “resolvido” quando o generator apontado por ele estiver em done: true, ou seja, quando ele tiver passado por todos os seus passos.

function* subTopics() { 
    yield "B1"; 
    yield "B2"; 
    yield "B3"; 
}

function* topics() { 
    yield "A"; 
    yield "B"; 
    yield* subTopics(); 
    yield "C"; 
}

var iterator = topics();

console.log(iterator.next()); // { value: "A", done: false } 
console.log(iterator.next()); // { value: "B", done: false } 
console.log(iterator.next()); // { value: "B1", done: false } 
console.log(iterator.next()); // { value: "B2", done: false } 
console.log(iterator.next()); // { value: "B3", done: false } 
console.log(iterator.next()); // { value: "C", done: false } 
console.log(iterator.next()); // { value: undefined, done: true }

O “Q” da questão aqui é que podemos utilizar o *yield** para generators, mas também para objetos iteráveis!

function* topics() { yield* [1, 2, 3]; }

var iterator = topics();

console.log(iterator.next()); // { value: 1, done: false } 
console.log(iterator.next()); // { value: 2, done: false } 
console.log(iterator.next()); // { value: 3, done: false } 
console.log(iterator.next()); // { value: undefined, done: true }

Ou até mesmo em strings:

function* topics() { 
    yield* "felipe"; 
}

var iterator = topics();

console.log(iterator.next()); // { value: "f", done: false } 
console.log(iterator.next()); // { value: "e", done: false } 
console.log(iterator.next()); // { value: "l", done: false } 
console.log(iterator.next()); // { value: "i", done: false } 
console.log(iterator.next()); // { value: "p", done: false } 
console.log(iterator.next()); // { value: "e", done: false } 
console.log(iterator.next()); // { value: undefined, done: true }

Concluindo

Enfim, como podem ver, trata-se de uma funcionalidade muito útil e que pode ser utilizada para “N” diferentes fins.

Caso queira se inteirar mais no assunto, separamos alguns links:

Por que não aproveita e deixa nos comentários uns testes e usos diferentes para generators e iterators?

Autor(a)

Co-founder/CTO at Nasc and @BrazilJS.
Software Engineer in love with the Web.
Google Developer Expert on Web Technologies, speaker, writer & teacher.