Geradores - JavaScript
Geradores representam um tipo especial de função usados para gerar valores. Para definir geradores, usa-se o símbolo de asterisco *
function
function* getNumber() {
yield 5;
}
const numberGenerator = getNumber();
const result = numberGenerator.next();
console.log(result); // {value: 5, done: false}
A função getNumber()
Um gerador é definido como uma função com o operador
(o asterisco vem após a palavrafunction*
).function
function* getNumber() { .... }
Para retornar um valor do gerador, usa-se o operador
, seguido pelo valor a ser retornado.yield
yield 5;
Neste caso, o gerador
gera o número 5.getNumber()
Para obter um valor do gerador, utiliza-se o método
.next()
const result = numberGenerator.next();
Ao chamar a função
, cria-se um objeto iterador, denominadogetNumber()
. Com esse objeto, podemos obter valores do gerador.numberGenerator
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
done
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()
{value: 5, done: false} {value: undefined, done: true}
Mas a função geradora getNumber()
undefined
true
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()
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
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()
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()
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
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()
Tratamento de Erros em Geradores
Com a função throw()
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
catch
error
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
{value: "Tom", done: false} Error: Something wrong {value: undefined, done: true}
Vale ressaltar que após a chamada da função throw()
next()
{value: undefined, done: true}