Javascript

Generators Functions - o que acontece por baixo dos panos

T
Por Tulio Faria 29 de junho de 2017
Generators Functions - o que acontece por baixo dos panos

Hoje vamos continuar falando sobre Generators Functions em JavaScript.

O que é Generator?

É uma função que podemos pausar ou iterar sobre ela. Já falamos sobre generators no hands-on do Módulo CO, que resolve um generator, mas hoje vamos falar como ele funciona ‘por baixo dos panos’ e até mesmo como o CO foi construído.

A primeira coisa que temos que saber é que a sintaxe do generator é criada com uma function e um asterisco. Se fizermos isso, dizemos que é uma função generator, então é possível pausar essa função com CO, o que deixa o código mais linear:

function* generator() {
  console.log('Entrou no generator')
}
const gen = generator()

Ao executar o código, perceba que não saiu nada. Primeiro precisamos iterar sobre ele para que consigamos ter um valor, para isso criamos um:

const iteration = gen.next()

O next é muito comum em outras linguagens, nas quais temos algumas estruturas que são iteráveis. Enquanto você utilizar o .next significa que você ainda tem dados para iterar.

Agora executamos para ver o que acontece novamente. Perceba que aparece a mensagem (‘Entrou no generator’) porque ele depende do next para entrar na primeira linha.

Agora vamos fazer algo diferente:

function* generator() {
  console.log('Entrou no generator')
  console.log('Segundo passo')
  console.log('Penultimo passo')
  console.log('fim do generator')
}
const gen = generator()
const iteration = gen.next()

Ele executou tudo de uma vez. Agora vamos fazer o seguinte:

yield 'outro valor'

Olha que interessante, ele parou agora no segundo passo, se eu der um console.log no iteration ele retorna um objeto com um value ‘outro valor’ dentro e um done: false. Ele parou e retornou esse valor, então ela é uma função que além de iterável, conseguimos ter vários retorno de valor. Se eu quisesse continuar executando, tenho que chamar novamente gen. next:

function* generator() {
  console.log('Entrou no generator')
  console.log('Segundo passo')
  yield 'outro valor'
  console.log('Penultimo passo')
  console.log('fim do generator')
}
const gen = generator()
const iteration = gen.next()
gen.next()

Perceba que ele fez os dois primeiros passos, pausou para retornar o yeild, e por fim fez os dois últimos passos, por isso é chamada de função pausavel.

No yeild, podemos por exemplo retornar uma promise:

function* generator(){
    console.log('Entrou no generator')
    console.log('Segundo passo')
    yield new Promise((resolve, reject) => {
        setTimeout(() => resolve(10),  2000
    })
    console.log('Penultimo passo')
    console.log('fim do generator')
}
const gen = generator()
const iteration = gen.next()
iteration.value.then(() => {
    gen.next()
})

Imagine que estamos buscando no banco de dados alguma informação; quando fazemos isso, estamos fazendo uma operação assíncrona e ela vai demorar mais tempo porque é um IO, o yeild está simulando essa busca e o iteration.value.then está esperando essa ‘busca’ para retornar as próximas informações.

Então o que o CO faz é o controle de chamar o next e executar o then de forma organizada. Vamos supor que esse código fosse um function ReadFilePromise:

function ReadFilePromise () {
    return new Promise((resolve, reject) => {
        setTimeout(() => resolve(10),  2000
    )})
}
function* generator(){
    console.log('Entrou no generator')
    console.log('Segundo passo')
    const value = yield readFilePromise()
    console.log('Penultimo passo' value)
    console.log('fim do generator')
}
const gen = generator()
const iteration = gen.next()
iteration.value.then(()=>{
    gen.next()
})

Ao fazer isso, ele vai chamar a nossa function ReadFilePromise e esperar. Para aproximar mais do CO o value tem que vir preenchido, mas perceba que ele veio com undefined porque não aproveitamos o value no then. Então vamos aproveitar:

const gen = generator()
const iteration = gen.next()
iteration.value.then((val) => {
  gen.next(val)
})

Assim vai ser retornado o penúltimo com o valor 10. O CO faz exatamente isso, conseguimos transformar facilmente em uma função genérica que resolva qualquer generator, por isso é tão legal resolver com promise, porque se fosse com readFile comum a gente não tem esse retorno de valor. Confira a dica em vídeo:

Curta o DevPleno no Facebook, inscreva-se no canal e não se esqueça de cadastrar seu e-mail para não perder as novidades. Abraço!