Atualizado: 07/12/2024

Geradores - JavaScript

Geradores representam um tipo especial de função usados para gerar valores. Para definir geradores, usa-se o símbolo de asterisco * após a palavra-chave function. Vejamos como definir um gerador simples:

function* getNumber() {
    yield 5;
}
const numberGenerator = getNumber();
const result = numberGenerator.next();
console.log(result);    // {value: 5, done: false}

A função getNumber() é um gerador. Os principais aspectos da criação e uso de um gerador são:

  • Um gerador é definido como uma função com o operador function* (o asterisco vem após a palavra function).

    function* getNumber() { .... }
  • Para retornar um valor do gerador, usa-se o operador yield, seguido pelo valor a ser retornado.

    yield 5;

    Neste caso, o gerador getNumber() gera o número 5.

  • Para obter um valor do gerador, utiliza-se o método next().

    const result = numberGenerator.next();

    Ao chamar a função getNumber(), cria-se um objeto iterador, denominado numberGenerator. Com esse objeto, podemos obter valores do gerador.

Ao observar a saída do console, vemos que este método retorna:

{value: 5, done: false}

Isso indica que o objeto retornado tem uma propriedade value que contém o valor gerado e uma propriedade done que indica se o fim do gerador foi alcançado.

Os geradores são semelhantes aos iteradores, mas são uma forma especial de iteradores.

Vamos alterar o código:

function* getNumber() {
    yield 5;
}
const numberGenerator = getNumber();
let next = numberGenerator.next();
console.log(next);
next = numberGenerator.next();
console.log(next);

A chamada ao método next() ocorre duas vezes:

{value: 5, done: false}
{value: undefined, done: true}

Mas a função geradora getNumber() gera apenas um valor, o número 5. Assim, em uma chamada subsequente, a propriedade value terá o valor undefined e a propriedade done será true, indicando que o gerador concluiu sua execução.

Um gerador pode criar/gerar vários valores:

function* getNumber() {
    yield 5;
    yield 25;
    yield 125;
}
const numberGenerator = getNumber();
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());
console.log(numberGenerator.next());

Saída do console:

{value: 5, done: false}
{value: 25, done: false}
{value: 125, done: false}
{value: undefined, done: true}

Para simplificar, podemos retornar elementos de um array em um gerador:


const numbers = [5, 25, 125, 625];
function* getNumber() {
    for(const n of numbers) {
        yield n;
    }
}
const numberGenerator = getNumber();
console.log(numberGenerator.next().value);  // 5
console.log(numberGenerator.next().value);  // 25

Entre duas chamadas consecutivas de next(), pode haver uma pausa indeterminada, durante a qual outras ações podem ocorrer, mas o gerador continuará a retornar o próximo valor:

const numberGenerator = getNumber();
console.log(numberGenerator.next().value);      // 5
// outras ações
    
console.log(numberGenerator.next().value);      // 25

Geradores não se limitam apenas aos operadores yield. Eles também podem conter lógicas mais complexas.

Geradores são úteis para criar sequências infinitas:

function* points() {
 
    let x = 0;
    let y = 0;
    while(true) {
        yield {x:x, y:y};
        x += 2;
        y += 1;
    }
}
let pointGenerator = points();
 
console.log(pointGenerator.next().value);
console.log(pointGenerator.next().value);
console.log(pointGenerator.next().value);

Saída do console:

{x: 0, y: 0}
{x: 2, y: 1}
{x: 4, y: 2}

Retorno do Gerador e a Função return

Como vimos anteriormente, cada chamada subsequente ao método next()retorna o próximo valor do gerador. No entanto, podemos encerrar a execução do gerador utilizando o método return():

function* getNumber() {
    yield 5;
    yield 25;
    yield 125;
}
const numberGenerator = getNumber();
console.log(numberGenerator.next());    // {value: 5, done: false}
numberGenerator.return();   // encerramos a execução do gerador
console.log(numberGenerator.next());    // {value: undefined, done: true}

Obtendo Valores do Gerador em um Loop

Como iteradores são utilizados para obter valores, podemos usar o loop for..of:

function* getNumber() {
    yield 5;
    yield 25;
    yield 125;
}
const numberGenerator = getNumber();
 
for(const num of numberGenerator) {
    console.log(num);
}

Saída do console:

5
25
125

Também podemos usar outros tipos de loops, como o loop while:

function* getNumber() {
    yield 5;
    yield 25;
    yield 125;
}
const numberGenerator = getNumber();
let item;
while(!(item = numberGenerator.next()).done) {
    console.log(item.value);
}

Passando Dados para o Gerador

Inicialização do Gerador

Assim como qualquer outra função, a função geradora pode aceitar parâmetros. Através dos parâmetros, podemos passar dados para o gerador. Por exemplo:

function* getNumber(start, end, step) {
    for(let n = start; n <= end; n += step) {
        yield n;
    }
}
const numberGenerator = getNumber(0, 8, 2);
 
for(const num of numberGenerator) {
    console.log(num);
}

Saída do console:

0
2
4
6
8

Outro exemplo é definir um gerador que retorna dados de um array:

function* generateFromArray(items) { 
    for(const item of items) 
        yield item;
}
const people = ["Tom", "Bob", "Sam"];
const personGenerator = generateFromArray(people);
for(const person of personGenerator)
    console.log(person);

Saída do console:

Tom
Bob
Sam

Passando Dados para o Método next

Com o método next(), podemos passar dados para o gerador. Os dados enviados a esse método podem ser capturados na função geradora através da chamada anterior do operador yield:

function* getNumber() {
    const n = yield 5;      // recebe o valor de numberGenerator.next(2).value
    console.log("n:", n);
    const m = yield 5 * n;  // recebe o valor de numberGenerator.next(3).value
    console.log("m:", m);
    yield 5 * m;
}
const numberGenerator = getNumber();
 
console.log(numberGenerator.next().value);      // 5
console.log(numberGenerator.next(2).value);     // 10
console.log(numberGenerator.next(3).value);     // 15

Saída do console:

5
n: 2
10
m: 3
15

No segundo chamado do método next():

numberGenerator.next(2).value

Os dados passados por ele podem ser capturados atribuindo o resultado da primeira chamada do operador yield:

Assim, a constante n será igual a 2, já que o número 2 é passado para o método next().

Depois, podemos usar esse valor para gerar um novo valor:

const m = yield 5 * n;
const m = yield 5 * n;

A constante m então receberá o valor passado pelo terceiro chamado do método next(), que é o número 3.

Tratamento de Erros em Geradores

Com a função throw(), podemos lançar uma exceção dentro do gerador. Um valor arbitrário, que representa a informação sobre o erro, é passado como parâmetro para essa função:

function* generateData() {
    try {
        yield "Tom";
        yield "Bob";
        yield "Hello Work";
    }
    catch(error) {
        console.log("Error:", error);
    }
} 
const personGenerator = generateData();
console.log(personGenerator.next());        // {value: "Tom", done: false{
personGenerator.throw("Something wrong");   // Error: Something wrong
console.log(personGenerator.next());        // {value: undefined, done: true{

Primeiramente, na função geradora, usamos a construção try..catch para lidar com possíveis exceções. No bloco catch, com o parâmetro error, podemos obter a informação sobre o erro que é passado para a função throw().

Quando usamos o gerador, podemos chamar essa função, passando informações arbitrárias sobre o erro (neste caso, é apenas uma mensagem de texto):

personGenerator.throw("Something wrong");

Essa chamada resultará em uma exceção na função geradora, e o controle será transferido para o bloco catch, que imprime a informação sobre o erro no console:

{value: "Tom", done: false}
Error: Something wrong
{value: undefined, done: true}

Vale ressaltar que após a chamada da função throw(), o gerador encerra sua execução, e na subsequente chamada do método next(), obteremos o resultado {value: undefined, done: true}.

Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com