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.