As funções do gerador são uma daquelas coisas que você pode não ver com tanta frequência. No entanto, eles podem ser úteis em algumas situações. Este tutorial ajudará você a entendê-los. Você aprenderá sobre o que são as funções do gerador. Você também vai aprender sobre yield
e next()
e também como delegar a execução.
De funções regulares a funções de gerador
Funções regulares existem desde o início do JavaScript. As funções são um dos blocos de construção fundamentais desta linguagem de programação. Isso não é verdade se falamos sobre funções geradoras. Essas funções especiais foram introduzidas no JavaScript recentemente.
As funções têm funcionado muito bem. Além das funções regulares, agora também existem funções de seta. Até agora, as funções das setas provaram ser benéficas em alguns casos. Freqüentemente, eles também são preferidos por desenvolvedores de JavaScript em relação ao normal. Alguém pode perguntar: por que adicionar algo novo?
As funções regulares e de seta são ótimas quando você deseja encapsular um trecho de código para torná-lo reutilizável. Eles também permitem que você retorne um único valor ou nada. Um único pode ser também uma matriz ou um objeto que contém vários valores. No entanto, ainda há uma coisa que você deseja devolver.
É aqui que as funções do gerador são diferentes. Ao contrário das funções regulares, os geradores são capazes de retornar vários valores. Dito isso, eles não devolvem todos ao mesmo tempo. Em vez disso, eles retornaram um após o outro, apenas quando você quiser. Até então, o gerador irá esperar enquanto lembra do último valor.
A sintaxe
Uma coisa boa sobre geradores é que eles têm uma sintaxe amigável. Há muito o que aprender, especialmente se você já conhece alguma coisa sobre funções regulares . Neste monumento, existem duas maneiras de criar uma função de gerador. O primeiro é com a ajuda do construtor GeneratorFunction .
Essa abordagem não é muito comum e você a verá muito raramente. A segunda abordagem, e mais comum, é usar a declaração de função . Sim, você também pode criar geradores com expressão de função. Em ambos os casos, a function
palavra-chave é seguida por um asterisco ( *
).
Este símbolo é o que diz ao JavaScript que você deseja criar uma função geradora, não uma função regular. Exceto esta pequena mudança, o resto da sintaxe é o mesmo que para a função. Há o nome da função, parênteses para parâmetros e corpo da função com o código a ser executado.
// Create generator function:
function* myGenerator() {
// Function body with code to execute.
}
O objeto gerador
Uma coisa que pode surpreendê-lo é que os geradores não executam o código dentro deles quando você os chama. Chamar uma função do gerador não executa o código dentro dela. Em vez disso, a função do gerador retornará um objeto especial denominado “objeto gerador”. Este objeto permite que você trabalhe com o gerador.
Ele permite que você diga ao gerador para retornar um novo valor quando você precisar dele, referenciando este objeto. Por causa disso, ao chamar uma função de gerador, você deve atribuir o objeto gerador retornado a alguma variável. Caso contrário, ele será perdido.
// Create generator function:
function* myGenerator() {
// Function body with code to execute.
}
// Assign the generator object to variable:
const myGeneratorObj = myGenerator()
// Log the generator object:
console.log(myGeneratorObj)
// Output:
// Iterator [Generator] {}
O rendimento e o próximo ()
Quando se trata de funções geradoras, há duas coisas importantes. A primeira é a palavra-chave yield , junto com alguma expressão. O segundo é o next()
método. A yield
palavra-chave é como um ponto de interrupção. Você pode usá-lo apenas em uma função de gerador. Ele faz duas coisas. Primeiro, ele retorna um valor do gerador.
A segunda coisa que ele faz é pausar a execução do gerador. Isso acontece logo depois que o gerador retorna o valor. Você pode pensar no yield
como uma return
declaração. A diferença é que enquanto o return
retorna e termina uma função, yield
retorna e apenas pausa um gerador.
Como você sabe, chamar uma função de gerador retorna um objeto de gerador. O next()
é o método principal deste objeto gerador. Este método permite que você execute o gerador, execute o código interno e retorne algum valor. O valor que ele retorna é especificado pela yield
palavra – chave. É precedido por ele.
Então, para resumir. O yield
permite que você retorne um valor do gerador, ao executá-lo, e então pause o gerador. O next()
método permite que você execute o gerador, retorne o valor que segue após o yield
. Lembre-se que next()
retornará apenas o valor após o primeiro yield
.
Se você usar cinco yield
palavras-chave em um gerador, terá que chamar o next()
método cinco vezes. Uma chamada para uma yield
. A cada rendimento, a execução de um gerador será pausada, aguardando outra chamada de next()
método.
// Create generator function:
function* myGenerator() {
// Use yield to return values:
yield 1
yield 2
yield 3
yield 4
return 5
}
// Assign the generator object to variable:
const myGeneratorObj = myGenerator()
// Return the first value:
console.log(myGeneratorObj.next())
// Output:
// { value: 1, done: false }
// Return the second value:
console.log(myGeneratorObj.next())
// Output:
// { value: 2, done: false }
// Return the third value:
console.log(myGeneratorObj.next())
// Output:
// { value: 3, done: false }
// Return the fourth value:
console.log(myGeneratorObj.next())
// Output:
// { value: 4, done: false }
// Return the fifth value:
console.log(myGeneratorObj.next())
// Output:
// { value: 5, done: true }
// The generator is finished.
// Try to return one more time:
console.log(myGeneratorObj.next())
// Output:
// { value: undefined, done: true }
Rendimento, próximo, valor e pronto
Quando você chama o next()
método, o JavaScript sempre retorna um objeto. Este objeto conterá duas propriedades com alguns valores. Uma propriedade será value
. Este é o valor real retornado pelo gerador. É o valor que segue após a yield
palavra – chave em seu código.
Se não houver valor a ser retornado, o valor dessa propriedade será undefined
. A segunda propriedade é done
. Esta propriedade informa se a função do gerador foi concluída ou não. “Concluído” significa que não há mais yield
palavras – chave nas funções do gerador e não há mais valores a serem retornados.
O valor de done
sempre será um booleano, true
ou false
. Será false
até que o gerador alcance o último yield
. Quando isso acontecer, ele retornará o último valor, após o último yield
, junto com done
definido como true
. Depois disso, ligar next()
novamente será inútil.
// Create generator function:
function* myGenerator() {
// Use yield to return values:
yield 'a'
yield 'b'
return 'omega'
}
// Assign the generator object to variable:
const myGeneratorObj = myGenerator()
// Return the first value:
console.log(myGeneratorObj.next())
// Output:
// { value: 'a', done: false }
// Return the second value:
console.log(myGeneratorObj.next())
// Output:
// { value: 'b', done: false }
// Return the third value:
console.log(myGeneratorObj.next())
// Output:
// { value: 'omega', done: true }
// This is the last value returned
// and the generator is finished.
Rendimento e retorno
Só porque os geradores costumam yield
retornar valores não significa que não haja lugar para return
declarações. Ainda existe. No contexto das funções geradoras, uma return
instrução é outra coisa que especifica se o gerador foi concluído. O gerador terminará em duas condições.
Primeiro, não há mais yield
palavras-chave. Em segundo lugar, a execução encontra uma return
declaração. Esses dois irão alterar o valor de done
no objeto retornado de false
para true
. Retornar um valor com retorno funciona assim yield
. Qualquer valor após o return
será o valor da value
propriedade no objeto retornado.
Três coisas para lembrar. Primeiro, return
vai terminar o gerador quer haja outro yield
ou não. Digamos que você declare quatro yields
em um gerador, mas coloque return
após o segundo. O resultado será que o gerador retornará três valores. Dois valores para os dois primeiros yield
e um para o return
.
Os dois últimos yield
após a instrução de retorno nunca serão executados porque o return
terminará o gerador prematuramente. A segunda coisa a lembrar é que você não precisa necessariamente usar a return
instrução. O gerador terminará quando encontrar o último yield
.
O terceiro para lembrar. Se você não usar return
, o valor done
após o último yield
ainda será definido como false
. Ele mudará apenas se você tentar retornar um valor mais uma vez. Com return
, done
será definido como true
correto com a última chamada de next()
método.
// Generator function without return:
// NOTE: last yield will not change "done" to "true".
// It will change only after another call of "next()".
function* myGeneratorOne() {
// Use yield to return values:
yield 'a'
yield 'b'
}
// Assign the generator object to variable:
const myGeneratorOneObj = myGeneratorOne()
// Return the first value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'a', done: false }
// Return the second value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'b', done: false }
// Try to return value again:
console.log(myGeneratorOneObj.next())
// { value: undefined, done: true }
// The generator is finished.
// Generator function ending with return:
// NOTE: the return will change "done" to "true" right away.
function* myGeneratorOne() {
// Use yield to return values:
yield 'a'
return 'b'
}
// Assign the generator object to variable:
const myGeneratorOneObj = myGeneratorOne()
// Return the first value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'a', done: false }
// Return the second value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'b', done: true }
// The generator is finished.
// Generator function with return in the middle:
function* myGeneratorOne() {
// Use yield to return values:
yield 'a'
yield 'b'
return 'End'
yield 'c'
yield 'd'
}
// Assign the generator object to variable:
const myGeneratorOneObj = myGeneratorOne()
// Return the first value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'a', done: false }
// Return the second value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'b', done: false }
// Return the third value (the return):
console.log(myGeneratorOneObj.next())
// Output:
// { value: 'End', done: true }
// The generator is finished.
// Try to return the fourth value:
console.log(myGeneratorOneObj.next())
// Output:
// { value: undefined, done: true }
Exemplo de função do gerador com um loop
Essa capacidade de retornar um valor sob demanda pode ser útil, por exemplo, quando você deseja gerar uma série de números com loop. Normalmente, o loop retornaria todos os números imediatamente. Isso não acontecerá se você usar funções geradoras. Os geradores permitirão que você retorne todos os números um por um.
Existem apenas algumas coisas que você precisa para criar este gerador de números. Primeiro, é uma função geradora. Dentro deste gerador haverá um loop. Dentro desse loop estará a yield
palavra – chave que retorna o número atual da série. Isso criará um loop que fará uma pausa após cada iteração, aguardando a próxima chamada de next()
.
// Example of generator with for loop:
function* myGenerator() {
for (let i = 0; i < 5; i++) {
yield i
}
}
// Assign the generator object to variable:
const myGeneratorObj = myGenerator()
// Return the first number:
console.log(myGeneratorObj.next())
// Output:
// { value: 0, done: false }
// Return the second number:
console.log(myGeneratorObj.next())
// Output:
// { value: 1, done: false }
// Return the third number:
console.log(myGeneratorObj.next())
// Output:
// { value: 2, done: false }
// Return the fourth number:
console.log(myGeneratorObj.next())
// Output:
// { value: 3, done: false }
// Return the fifth number:
console.log(myGeneratorObj.next())
// Output:
// { value: 4, done: false }
// Try to return another number:
console.log(myGeneratorObj.next())
// Output:
// { value: undefined, done: true }
// The generator is finished.
Rendimento * e delegação de execução
A yield
palavra-chave pode fazer mais do que apenas retornar um valor. Ele também permite que você delegue a execução do gerador a outro gerador. Você pode usá-lo para iniciar um segundo gerador do primeiro. Este segundo gerador funcionará até que seja concluído. Em seguida, a execução continuará para o primeiro gerador.
Isso permite que você conecte vários geradores. Em seguida, você pode executá-los em uma série que pode controlar, cada um pelo tempo necessário. Quando você quiser usar o yield
para fazer isso, lembre-se de adicionar o símbolo de asterisco ( *
) logo após a yield
palavra – chave ( yield*
). Em seguida, adicione uma chamada ao gerador que deseja executar.
// Create first generator function:
function* myGeneratorOne() {
yield 1
yield* myGeneratorTwo() // Delegate to myGeneratorTwo() generator.
yield 3
}
// Create second generator function:
function* myGeneratorTwo() {
yield 'a'
yield 'b'
yield 'c'
}
// Assign the first generator object to variable:
const myGeneratorObj = myGeneratorOne()
// Return the first value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: 1, done: false }
// Return the second value (myGeneratorTwo):
console.log(myGeneratorObj.next())
// Output:
// { value: 'a', done: false }
// Return the third value (myGeneratorTwo):
console.log(myGeneratorObj.next())
// Output:
// { value: 'b', done: false }
// Return the fourth value (myGeneratorTwo):
console.log(myGeneratorObj.next())
// Output:
// { value: 'c', done: false }
// Return the fifth value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: 3, done: false }
// Return the sixth value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: undefined, done: true }
Rendimento * e declaração de retorno
Quando você usa a delegação, cuidado com as return
declarações. Isso se aplica especialmente a geradores em algum lugar da série. Não se preocupe. A return
declaração não vai terminar, ou encerrar, toda a cadeia. Ele só vai terminar o gerador em que está. No entanto, ele não retornará nenhum valor.
Quando você usa a return
instrução em um gerador, ele encerrará o gerador. Ele também retornará um valor que o segue. Isso não se aplica à execução delegada e à cadeia de geradores. Neste caso, return
apenas finalizará o gerador de corrente e retomará a execução do anterior. Não retornará um valor.
// Create first generator function:
function* myGeneratorOne() {
yield 1
yield* myGeneratorTwo() // Delegate to myGeneratorTwo() generator.
yield 3
}
// Create second generator function:
function* myGeneratorTwo() {
yield 'a'
yield 'b'
return 'c' // This returned value will not show up.
}
// Assign the first generator object to variable:
const myGeneratorObj = myGeneratorOne()
// Return the first value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: 1, done: false }
// Return the second value (myGeneratorTwo):
console.log(myGeneratorObj.next())
// Output:
// { value: 'a', done: false }
// Return the third value (myGeneratorTwo):
console.log(myGeneratorObj.next())
// Output:
// { value: 'b', done: false }
// Return the fourth value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: 3, done: false }
// Return the fifth value (myGeneratorOne):
console.log(myGeneratorObj.next())
// Output:
// { value: undefined, done: true }
Yield, next () e passando argumentos
Há uma coisa interessante sobre o next()
método. Ele permite que você passe argumentos para funções geradoras. Quando você passa algo como um argumento para o next()
, esse valor se tornará o valor de yield
no gerador. Dito isso, se você quiser passar algum argumento, faça isso na segunda chamada de next()
, não na primeira.
A razão para isso é simples. A primeira chamada do next()
método inicia a execução do gerador. O gerador é então pausado quando atinge o primeiro yield
. Não existe yield
entre o início da execução do gerador e o primeiro yield
. Portanto, qualquer argumento que você passar será perdido.
// Create generator function:
function* myGenerator() {
console.log(yield + 1)
console.log(yield + 2)
console.log(yield + 3)
console.log(yield + 4)
return 5
}
// Assign the first generator object to variable:
const myGeneratorObj = myGenerator()
// Return the first value (no argument passing):
console.log(myGeneratorObj.next())
// Output:
// { value: 1, done: false }
// '1x' // <= value from console.log(yield + ...)
// Return the second value:
console.log(myGeneratorObj.next('1x'))
// Output:
// { value: 2, done: false }
// '2x' // <= value from console.log(yield + ...)
// Return the third value:
console.log(myGeneratorObj.next('2x'))
// Output:
// { value: 3, done: false }
// '3x' // <= value from console.log(yield + ...)
// Return the fourth value:
console.log(myGeneratorObj.next('3x'))
// Output:
// { value: 4, done: false }
// '4x' // <= value from console.log(yield + ...)
// Return the fifth value:
console.log(myGeneratorObj.next('4x'))
// Output:
// { value: 5, done: true }
Conclusão: Funções do gerador de JavaScript simplificadas
As funções do gerador podem não ser usadas com tanta frequência, mas podem ser úteis. Por exemplo, quando você deseja gerar alguns dados sob demanda. Ou, quando você deseja ter mais controle sobre a iteração de alguns dados. Espero que este tutorial tenha ajudado você a entender o que são funções geradoras e como trabalhar com elas.