Skip to content

Instantly share code, notes, and snippets.

@dreamorosi
Last active April 20, 2017 09:53
Show Gist options
  • Save dreamorosi/e19ce9f410cf33e18b08573c6bb485d3 to your computer and use it in GitHub Desktop.
Save dreamorosi/e19ce9f410cf33e18b08573c6bb485d3 to your computer and use it in GitHub Desktop.
Javascript ES6 generators

Javascript ES6 generators

What is a generator?

Generators are functions that can be paused and resumed arbitrarily. The normal behavior of a Javascript function would be to execute its entire body when called. A generator's body instead can be divided in blocks of code that will be executed when a certain method is called on a generator's instance.

To create a generator we add an asterisk after the function keyword.

function* createLogger () {
  // your code
}

When a generator is called instead of executing its body it returns an instance of itself so in order to execute the first block we can just create an instance, assign it to a variable and call the .next() method on it.

function* createLogger () {
  console.log('Hello')
}

const logger = createLogger()
logger.next()
// outputs -> Hello

To pause a generator we use the yield keyword that tells the generator to stop executing. In this example we have two separated blocks so if we call the .next() method once the output will be the same as before.

function* createLogger () {
  console.log('Hello')
  yield
  console.log('World')
}

const logger = createLogger()
logger.next()
// outputs -> Hello

If we call the method again both blocks will be executed. We can have as many blocks as we want.

...

const logger = createLogger()
logger.next()
logger.next()
// outputs -> Hello
// outputs -> World

Pass data to and from the generator

A generator and an instance can communicate with each other by using the yield keyword.

To output data from a generator just put it after the yield.

function* createHello () {
  yield 'Hello'
}

const hello = createHello()
console.log(hello.next())
// outputs -> { value: 'Hello', done: false }

The generator's output isn't just a value but an object that contains the returned value and a done variable that is set to false until the last block is executed.

...

const hello = createHello()
console.log(hello.next())
console.log(hello.next())
// outputs -> { value: 'Hello', done: false }
// outputs -> { value: undefined, done: true }

If we want to pass a value from an instance to a generator instead we can do so by passing it as an argument of the .next() method when resuming execution.

function* createHello () {
  const word = yield
  console.log(word)
}

const hello = createHello()
console.log(hello.next())
console.log(hello.next('Hello'))
// outputs -> { value: undefined, done: false }
// outputs -> Hello
// outputs -> { value: undefined, done: true }

Error handling

While the generator's code seems synchronous its behavior is essentially asynchronous because when its execution is paused we can keep doing things. For this reason if something goes wrong between two .next() calls we need a way to tell the generator that an error has happened; we can do so by using the .throw() method on an instance.

For an effective error handling we can use a standard try { ... } catch { ... }.

function* createHello () {
  try {
    let word = yield
    console.log(`Hello ${word}`)
  } catch (err) {
    console.log('Error', err)
  }
}

Considering the function above if we create an instance and we pass a value in the second .next() the block inside the try will be executed.

...

const hello = createHello()
hello.next()
hello.next('World')
// outputs -> Hello World

If we decide to throw and error instead the catch block will be executed with the argument value.

...

const hello = createHello()
hello.next()
hello.throw('Something went wrong')
// outputs -> Error Something went wrong

Iteration through blocks

We can iterate through blocks of code inside a generator in two ways, one involves manually calling the .next() method until done: true while the other offers a more concise and elegant way.

Consider the following generator that simply yields some values.

function* createCounter () {
  yield 1
  yield 2
  yield 3
  yield 4
}

Instead of calling the .next() method manually we can use a for...of and iterate through the instance.

...

const counter = createCounter()
for (let i of counter) {
  console.log(i)
}
// outputs -> 1
// outputs -> 2
// outputs -> 3
// outputs -> 4
// outputs ->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment