{ Generators and Async Functions. }

Objectives:

By the end of this chapter, you should be able to:

  • Explain what generator functions are and how they differ from ordinary functions
  • Use generators to manage asynchronous code
  • Explain ES2017 async function syntax and how async functions work

Generators

In ES2015, a special type of function called a generator was introduced. Generator functions are functions that can return multiple values using the yield keyword. Previously, we have only seen functions that return once, but generators can change that for us. Let's start with a simple generator example (generator functions are denoted using a *):

function* firstGenerator() {
  for (var i = 0; i < 5; i++) {
    yield i;
  }
}

var gen = firstGenerator();
gen.next(); // {value: 0, done: false}
gen.next(); // {value: 1, done: false}
gen.next(); // {value: 2, done: false}
gen.next(); // {value: 3, done: false}
gen.next(); // {value: 4, done: false}
gen.next(); // {value: 5, done: true}

// we can also iterate over a generator using a for..of loop

for (var data of firstGenerator()) {
  console.log(data);
}

// 0
// 1
// 2
// 3
// 4

Let's now see what our earlier async example would look like if we used generators:

function getJokesAbout(term) {
  $.getJSON(
    `https://icanhazdadjoke.com/search?term=${term}`,
    function(data) {
      // we could also do gen.next(data) to make this function run all three at once
      console.log(data);
    },
    function(err) {
      console.log(err);
    }
  );
}

function* displayResults() {
  var result1 = yield getJokesAbout("spider");
  console.log(result1);
  var result2 = yield getJokesAbout("ghost");
  console.log(result2);
  var result3 = yield getJokesAbout("pizza");
  console.log(result3);
}

var gen = displayResults();

// if we want to print all without using next()
for (var jokeData of displayResults()) {
  console.log(jokeData);
}

While this may seem simple to look at, generators are not always the easiest to read and can add a great deal of complexity quickly.

A better option is to use ES2017 async functions.

ES2017 Async Functions

As of ES2017, two new keywords have been added to the language: async and await. These two keywords allow us to write code that "looks" synchronous, but is actually asynchronous. We prefix our functions with the async keyword to denote that they are actually asynchronous and we invoke those functions with the keyword await to ensure that they have completed before their values are stored. These keywords are actually not new to programming languages; both Python and C# have the same concept.

async and await are relatively new additions to the language, so in order to run all of these code examples, make sure you're using Node version 7.6 or higher, or are working in a modern browser (the most recent version of Chrome will suffice).

Note that when you make a funciton an async function, it automatically returns a promise to you. Very commonly you'll create the promise yourself, but even if you don't, the value you return will be wrapped in a promise:

async function asyncExample() {
  return 1;
}

asyncExample(); // PromiseĀ {<resolved>: 1}

Let's revisit our example from before, but refactor it one more time to use async and await! Try this out in the browser:

// Using Promises alone
async function logJokesAbout(term) {
  var data = await $.getJSON(`https://icanhazdadjoke.com/search?term=${term}`);
  console.log("Here is the joke data!", data);
}

logJokesAbout("spider");
logJokesAbout("pizza");
// etc.

Remember: if you return the data rather than logging it, you'll get a promise, not the actual data!

For more on async and await, check out the MDN guide to async functions.

When you're ready, move on to Asynchronous JavaScript Exercises

Continue

Creative Commons License