Dirceu Heineck
4/2/2022 Dirceu Heineck

Generator function no Javascript

blog-feature-image

📑 Sobre

Geradores são funçÔes cuja execução pode ser interrompida e posteriormente reconduzida. Seus contextos (de associaçÔes de variåveis) ficarão salvos entre cada recondução. A declaração function* (palavra chave function seguida de um asterisco) define uma função geradora que retorna um objeto Generator.

📚 Alguns exemplos da função

⭐ FunçÔes geradoras são pilares para uma programação assíncrona que mitigam os problemas com callbacks. Ao invocar uma função geradora sua implementação não é executada imediatamente. Ao invés disso é retornado um iterator.

function* proximoID() {
  let index = 0
  while (true) yield index++
}
const generatorID = proximoID()
console.log(gen.next().value) // 0
console.log(gen.next().value) // 1
console.log(gen.next().value) // 2

⭐ Yield define o valor que retorna de uma generator function via o protocolo iterator. Se omitido, serĂĄ retornado undefined. Se return(value) for chamado em um gerador que jĂĄ estĂĄ no estado “concluĂ­do”, o gerador permanecerĂĄ no estado “concluĂ­do”. Se nenhum argumento for fornecido, a propriedade value do objeto retornado serĂĄ “indefinida”. Se um argumento for fornecido, ele serĂĄ definido como o valor da propriedade value do objeto retornado.

function* gen() {
  yield 1
  yield 2
  yield 3
}

const g = gen()
g.next() // { value: 1, done: false }
g.next() // { value: 2, done: false }
g.next() // { value: 3, done: false }
g.next() // { value: undefined, done: true }
g.return() // { value: undefined, done: true }
g.return(1) // { value: 1, done: true }

⭐ Ao chamar a função next() passando um valor como parùmetro a função interna do gerador pode atribuir a esse valor.

function* fib() {
  let a = 1
  let b = 1
  while (true) {
    let curr = b
    b = a
    a = a + curr
    const reset = yield curr
    if (reset) a = b = 1
  }
}
const sequence = fib()
console.log(sequence.next().value) //1
console.log(sequence.next().value) //1
console.log(sequence.next().value) //2
console.log(sequence.next().value) //3
console.log(sequence.next().value) //5
console.log(sequence.next(true).value) //1
console.log(sequence.next().value) //1
console.log(sequence.next().value) //2
console.log(sequence.next().value) //3

⭐ É importante notar que o primeiro next() que Ă© chamado nĂŁo passarĂĄ um valor. Ele apenas iniciarĂĄ o gerador. Para demonstrar isso, podemos registrar o valor do yield e chamar o next() algumas vezes com alguns valores.

function* generatorFunction() {
  console.log(yield)
  console.log(yield)

  return 'The end'
}

const generator = generatorFunction()

generator.next()
generator.next(100)
generator.next(200)

Isso darĂĄ o seguinte resultado:

Output
100
200
{value: "The end", done: true}

⭐ Generators computam seus valores “yielded” por demanda, que os permitem representar sequĂȘncias de forma eficiente que costumam ser trabalhosas ao serem computadas, ou atĂ© sequĂȘncias infinitas. Aqui um gerador de sequĂȘncia Fibonacci usando next(x) pra restartar a sequĂȘncia. A SequĂȘncia de Fibonacci Ă© uma sequĂȘncia numĂ©rica infinita que foi elaborada pelo matemĂĄtico italiano Leonardo Pisa. Começando pelo nĂșmero 1, a sequĂȘncia Ă© formada pela soma de cada numeral com o nĂșmero que o antecede. Ou seja, 1 + 1 = 2, 2 + 1 = 3, 3 + 2 = 5, e assim por diante.

function* fibonacci() {
  var fn1 = 1
  var fn2 = 1
  while (true) {
    var current = fn2
    fn2 = fn1
    fn1 = fn1 + current
    var reset = yield current
    if (reset) {
      fn1 = 1
      fn2 = 1
    }
  }
}

var sequence = fibonacci()
console.log(sequence.next().value) // 1
console.log(sequence.next().value) // 1
console.log(sequence.next().value) // 2
console.log(sequence.next().value) // 3
console.log(sequence.next().value) // 5
console.log(sequence.next().value) // 8
console.log(sequence.next().value) // 13
console.log(sequence.next(true).value) // 1
console.log(sequence.next().value) // 1
console.log(sequence.next().value) // 2
console.log(sequence.next().value) // 3

📚 for..of

Com especificaçÔes do ES6 podemos utilizar o loop do for..of para iterar sobre um gerador. O iterator criado pela função geradora Ă© capturada pelo for e automaticamente iterada sobre seus valores atĂ© encontrar o done. O valor na Ășltima iteração tambĂ©m nĂŁo Ă© executa jĂĄ que a condição done=true foi satisfeita na Ășltima iteração do laço.

function* foo() {
  yield 1
  yield 3
  yield 4
  return 8
}
for (let v of foo()) console.log(v) // 1234

⭐ Sabemos que para salvar na base Ă© um processo assĂ­ncrono, entĂŁo se vocĂȘ utilizar um looping convencional, um for, forEach, map, vocĂȘ notarĂĄ que todos registros vĂŁo ser processados quase ao mesmo tempo, gerando assim uma grande demanda do processador e memĂłria, jĂĄ com o generator, vocĂȘ consegue processar individualmente cada item da lista e esperar pelo retorno de cada um, assim vocĂȘ consegue controlar como vai ser percorrido essa lista.

/*
 * looping convencional
 */
const saveUser = async name => {
  console.log('Iniciado processo para salvar usuĂĄrio: ', name)

  const timeToAwait = Math.floor(Math.random() * (5000 - 2000)) + 2000

  return new Promise(resolve =>
    setTimeout(() => {
      console.log(`UsuĂĄrio ${name} salvo`)
      resolve()
    }, timeToAwait)
  )
}

const users = ['Roberto', 'Dirceu', 'Pablo', 'Anderson']

users.map(saveUser)

/*
Iniciado processo para salvar usuĂĄrio: Roberto
Iniciado processo para salvar usuĂĄrio: Dirceu
Iniciado processo para salvar usuĂĄrio: Pablo
Iniciado processo para salvar usuĂĄrio: Anderson
UsuĂĄrio Dirceu salvo
UsuĂĄrio Pablo salvo
UsuĂĄrio Roberto salvo
UsuĂĄrio Anderson salvo
*/
/*
 * looping usando Generator
 */
const saveUser = async name => {
  console.log('Iniciado processo para salvar usuĂĄrio: ', name)

  /*
   * Tempo aleatĂłrio para simbolizar um processo assĂ­ncrono
   */
  const timeToAwait = Math.floor(Math.random() * (5000 - 2000)) + 2000

  /*
   *  Simulando um processo assĂ­ncrono ao salvar o usuĂĄrio na base de dados
   */
  return new Promise(resolve =>
    setTimeout(() => {
      console.log(`UsuĂĄrio ${name} salvo`)
      resolve()
    }, timeToAwait)
  )
}

const users = ['Roberto', 'Dirceu', 'Pablo', 'Anderson']

function* createGenerator(list) {
  yield* list
}

const usersGenerator = createGenerator(users)

for await (let user of usersGenerator) {
  await saveUser(user)
}

/*
Iniciado processo para salvar usuĂĄrio: Roberto
UsuĂĄrio Roberto salvo
Iniciado processo para salvar usuĂĄrio: Dirceu
UsuĂĄrio Dirceu salvo
Iniciado processo para salvar usuĂĄrio: Pablo
UsuĂĄrio Pablo salvo
Iniciado processo para salvar usuĂĄrio: Anderson
UsuĂĄrio Anderson salvo
*/

📚 Done

Como percebemos acima usamos o Done ao fim de qualquer uma das funçÔes. Onde nenhuma das expressÔes podem ser encontradas o generator emite um {done: true}. Uma vez que {done:true} foi emitido, todas as chamadas subsequentes a g.next() vão ser completamente ignoradas e o generator vai retornar {done:true}, pois é o done que diz se acabou os passos do generator.

function* generator() {
  yield 'yeah'
}

var g = generator()
console.log(g.next()) // { done: false, value: 'yeah' }
console.log(g.next()) // { done: true }
console.log(g.next()) // { done: true }

🔗 ReferĂȘncias

VocĂȘ pode estudar um pouco mais do assunto nos links:


Se vocĂȘ curtiu esse conteĂșdo deixe o seu like e compartilhe com a galera. 😋

Dirceu Heineck.

comments powered by Disqus

NOS ACOMPANHE NAS REDES SOCIAIS