Funções Geradoras do ES6

O ES6 introduziu as funções geradoras, que permitem suspender a execução da função através da palavra-chave yield, oferecendo novas possibilidades para controlar o fluxo de execução e fornecendo soluções para programação assíncrona.

Composição das Funções Geradoras

As funções geradoras possuem duas características que as distinguem das funções comuns:

  • A presença de um asterisco (*) após a palavra function e antes do nome da função;
  • A existência de expressões yield dentro do corpo da função.

O asterisco indica que a função é um gerador, enquanto o yield define pontos de suspensão dentro da função.

function* gerador(){
 console.log("primeiro");
 yield 'resultado1';
 console.log("segundo");
 yield 'resultado2'; 
 console.log("terceiro");
 return 'final';
}

Mecanismo de Execução

Ao chamar uma função geradora, diferente das funções tradicionais, ela não é executada imediatamente. Em vez disso, retorna um objeto iterador que aponta para o estado interno da função. Para executar o código, é necessário chamar o método next() deste iterador, que fará com que a execução comece do início ou do último ponto de suspensão.

const iteravel = new gerador();<br></br>iteravel.next();
// primeiro
// {value: "resultado1", done: false}
 
iteravel.next();
// segundo
// {value: "resultado2", done: false}
 
iteravel.next();
// terceiro
// {value: "final", done: true}
 
iteravel.next();
// {value: undefined, done: true}

A execução começa no topo da função geradora, imprimindo "primeiro". Ao encontrar um yield, a execução é pausada e o valor seguinte é retornado como a propriedade value do objeto.

Métodos do Objeto Iterador Retornado pela Função

Método next

Normalmente, quando o método next() é chamado sem parâmetros, a expressão yield retorna undefined. Quando um parâmetro é fornecido ao next(), este valor é retornado pela expressão yield anterior.

function* processarParametros(){
    console.log("iniciando");
    var x = yield 'passo1';
    console.log("valor1:" + x);
    var y = yield 'passo2';
    console.log("valor2:" + y);
    console.log("soma:" + (x + y));
}

Chamando next sem parâmetros

const iterador1 = processarParametros();
iterador1.next();
// iniciando
// {value: "passo1", done: false}
iterador1.next();
// valor1:undefined
// {value: "passo2", done: false}
iterador1.next();
// valor2:undefined
// soma:NaN
// {value: undefined, done: true}

Chamando next com parâmetros

const iterador2 = processarParametros();
iterador2.next(10);
// iniciando
// {value: "passo1", done: false}
iterador2.next(20);
// valor1:20
// {value: "passo2", done: false}
iterador2.next(30);
// valor2:30
// soma:50
// {value: undefined, done: true}

Besides using next, it's also possible to use the for...of loop to traverse the Iterator object produced by a Generator function.

Método return

O método return() finaliza a execução da função geradora e retorna um valor especificado.

function* sequencia(){
    yield 1;
    yield 2;
    yield 3;
}
const g = sequencia();
g.next();
// {value: 1, done: false}
g.return("terminado");
// {value: "terminado", done: true}
g.next();
// {value: undefined, done: true}

Método throw O método throw() permite lançar uma exceção externamente à função geradora, que pode ser capturada internamente.

const geradorExcecao = function* () {
  try {
    yield;
  } catch (erro) {
    console.log('capturado internamente', erro);
  }
};
 
const iteradorExcecao = geradorExcecao();
iteradorExcecao.next();
 
try {
  iteradorExcecao.throw('erro1');
  iteradorExcecao.throw('erro2');
} catch (e) {
  console.log('capturado externamente', e);
}
// capturado internamente erro1
// capturado externamente erro2

O iterador lançou dois erros. O primeiro foi capturado dentro da função geradora, enquanto o segundo, já que o bloco catch interno já havia sido executado, não foi mais capturado, sendo propagado para fora e capturado pelo bloco catch externo.

Expressão yield*

A expressão yield* indica que o yield deve retornar um objeto iterador, permitindo que uma função geradora chame outra função geradora internamente.

function* receptora() {
    console.log('receptora: ' + (yield));
}
function* chamadora() {
    while (true) {
        yield* receptora();
    }
}
const iteradorChamador = chamadora();
iteradorChamador.next();
// {value: undefined, done: false}
iteradorChamador.next("dado1");
// receptora: dado1
// {value: undefined, done: false}
iteradorChamador.next("dado2");
// receptora: dado2
// {value: undefined, done: false}
 
// Equivalente a
function* chamadora() {
    while (true) {
        for (var valor of receptora) {
          yield valor;
        }
    }
}

Casos de Uso

Implementação de Iterator

Fornecer métodos de iteração para objetos que não possuem a interface Iterator nativamente.

function* propriedadesObjeto(obj) {
    const chaves = Reflect.ownKeys(obj);
    for (const chave of chaves) {
        yield [chave, obj[chave]];
    }
}
 
const usuario = { nome: 'João', sobrenome: 'Silva' };
for (const [chave,valor] of propriedadesObjeto(usuario)) {
    console.log(`${chave}: ${valor}`);
}
// nome: João
// sobrenome: Silva

O método Reflect.ownKeys() retorna todas as propriedades de um objeto, incluindo as não enumeráveis e as do tipo Symbol. Objetos JSON não possuem a interface Iterator nativamente e não podem ser iterados com for...of. Ao utilizar uma função geradora, é possível adicionar essa interface e permitir a iteração sobre objetos JSON.

Tags: ES6 funções-geradores javascript yield iteradores

Publicado em 6-17 21:18