{ 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 how ES2017 async functions work and their syntax

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 async example would look like if we used generators:

function getMovieData(title){
    $.getJSON(`http://www.omdbapi.com/?t=${title}`, 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 getMovieData("Titanic");
    console.log(result1);
    var result2 = yield getMovieData("Ghostbusters");
    console.log(result2);
    var result3 = yield getMovieData("Sharknado");
    console.log(result3);
}

var gen = displayResults();
// gen.next(); // Titanic
// gen.next(); // Ghostbusters
// gen.next(); // Sharknado

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

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

In the next version of JavaScript, two keywords will be 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.

If you are using version 7.6 or higher of Node.js - you will not need a transpiler and can run the code in the script.js file below

If you are using an older version of chrome, a different browser or an older version of node, in order to use ES2017, you will need to use a transpiler (turn ES2017 code into ES5/ES2015 code that the browser understands). We will use babel as our transpiler, and in order to transpile our code we will use the babel-cli. In a more serious development environment we would use a module bundler like webpack, but that is a bit too advanced right now. To get started with this, make sure you have node.js installed (you can install it here) and run the following steps

mkdir learn_async_await && cd learn_async_await
npm init -y
npm install --save-dev babel-cli babel-preset-latest request
echo '{ "presets": ["latest"] }' > .babelrc
# if you are using version 7.6 or greater of node, just run:
    # npm install --save request
touch script.js

Inside our script.js, let's make an API request to get some movie data.

// the request module allows us to make server side API calls
var request = require('request');

// Using Promises alone
function getMovieWithPromises (title) {
    return new Promise(function(resolve, reject) {
        request(`http://www.omdbapi.com/?t=${title}`, function(err, res, body){
        if (err) {
            reject(err);
        }
        resolve(body);
        });
    });
}


function showDataWithPromises(title){
    getMovieWithPromises(title).then(function(data){
        console.log("movie loaded!");
        console.log(data);
    }).catch(function(err){
        console.log(err);
    })
}

showDataWithPromises("Titanic");

// Using Async Functions

    // make use of the previously defined getMovieWithPromises since async functions return a Promise
async function showDataWithAsync(title){
  var data = await getMovieWithPromises(title);
  console.log(data);
}

// since async functions return a promise we can even chain them if we'd like!
showDataWithAsync("Titanic").then(function(data){
    console.log("movie loaded!");
    console.log(data);
}).catch(function(err){
    console.log("Something went wrong!", err);
})

// We can operate on the result of these functions just like with Promises
Promise.all([showDataWithAsync("Titanic"), showDataWithAsync("Ghostbusters")]).then(function(data){
    console.log(data);
});

To run this code, make sure you are in the terminal in the same directory as where your script.js file is and you can type node script.js (if you are using a transpiler - ./node_modules/.bin/babel-node script.js).

The value of async functions is that we can write code that looks very synchronous and readable and still make use of Promises, which have a friendly API. However, async and await are not supported by most browsers so you will need to use a transpiler like babel for this code to work.

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

Continue