Menu Close

Functional JavaScript — Generators

JavaScript is partly a functional language.

To learn JavaScript, we got to learn the functional parts of JavaScript.

In this article, we’ll look at JavaScript generators.

Callback Hell

If we some functions like:

let sync = () => {
  //..
}
let sync2 = () => {
  //...
}
let sync3 = () => {
  //...
}

and each function is synchronous, then we can call it one by one and compose them our way.

But if they’re async, then we can’t call one by one.

An async function may have a callback to let us call the callback when we get the result:

let async1 = (fn) => {
  //...
  fn( /* result data */ )
}
let async2 = (fn) => {
  //...
  fn( /* result data */ )
}
let async3 = (fn) => {
  //...
  fn( /* result data */ )
}

Then if we want to call them sequentially, we’ve to write something like:

async1(function(x) {
  async2(function(y) {
    async3(function(z) {
      //...
    });
  });
});

As we can see, there’s a lot of nesting in our code.

We got to make this cleaner.

Generators

We can clean up this code with generators.

To create a generator function, we can write:

function* gen() {
  return 'foo';
}

A generator function is indicated by the function* keyword.

Then we can call it to create a generator:

let generator = gen();

The generatorResult object has the next method to return the value we yielded.

The returned object has the value and done properties.

So we can write:

generator.next().value

and we get 'foo' .

Calling next for a second time will return an object with the value being undefined .

The yield keyword is a new keyword that will bring value to the next method.

The value of each yield statement will return in the sequence they’re listed.

yield pauses the execution of the function and sends back the result to the caller.

Then next time next is called, the next yield statement is run.

So if we have:

function* gen() {
  yield 'first';
  yield 'second';
  yield 'third';
}

let generator = gen();

The first generator.next() call returns:

{value: "first", done: false}

Then 2nd call returns:

{value: "second", done: false}

The 3rd call returns:

{value: "third", done: false}

The 4th call returns:

{value: undefined, done: true}

which indicates that the generator has no more values to return.

The done property indicates whether the generator bis done with returning all the values.

We know when to stop calling next once done is true .

Passing Data to Generators

We can also pass data to generators.

To do that, we create a generator function with a yield statement that has no value after it.

For example, we can write:

function* fullName() {
  const firstName = yield;
  const lastName = yield;
  console.log(`${firstName} ${lastName}`);
}

We have the yield statement without a value after it, so it’ll accept values we pass into next .

Then we can use it by writing:

const fn = fullName();
fn.next()
fn.next('mary')
fn.next('jones')

We create the generator function.

Then we call next to start the generator.

Once we did that, we can start passing values into the generator.

We call:

fn.next('mary')

to pass in a value to the first yield statement.

Then we do the same with the 2nd one with:

fn.next('jones')

Once we did that, we get 'mary jones' from the console log.

Async Code and Generators

The async and await syntax is created based on generators.

For instance, we can use it by writing:

const getData = async () => {
  const res = await fetch('https://api.agify.io/?name=michael')
  const data = await res.json();
  console.log(data);
}

We have the async and await syntax.

await pauses the execution of getData until the result is present.

So it acts like yield .

Now we run our async code line by line.

The only difference is that await only works with promises.

Conclusion

We can use generators to return items in sequence.

The function is paused after a result is returned and resumed when the next result is requested.

The async and await syntax for promises is based on the generator syntax.

Posted in Functional JavaScript