Free Online Courses for Software Developers - MrBool
× Please, log in to give us a feedback. Click here to login
×

You must be logged to download. Click here to login

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

×

MrBool is totally free and you can help us to help the Developers Community around the world

Yes, I'd like to help the MrBool and the Developers Community before download

No, I'd like to download without make the donation

Understanding async and await in JavaScript

In this article we will see a fast overview around these two important funcions in JavaScript: async and await.

Features like async/await failed to come to the ES6, but that does not mean they will not get to JavaScript. As I write this post, it is a proposal in phase 3 and is being actively worked on. The features are already in the Edge and should reach other browsers once it arrives in phase 4 - paving their way for inclusion in the next edition of the language.

Using Promises

Let's assume that we have the code in Listing 1. Here we are encapsulating an HTTP call on a Promise. The promise runs the body if success and is rejected with an err otherwise. It pulls the HTML of a random article of a single website every time it is performed.

Listing 1. Promise example.

var request = require('request');
 
function getRandomSiteFooArticle () {
  return new Promise((resolve, reject) => {
    request('https://site.com/articles/random', (err, res, body) => {
      if (err) {
        reject(err); return;
      }
      resolve(body);
    });
  });
}

A typical use of the promise shown above is in the code below. In it, we have built a promise thread turning the Markdown page HTML of a subset of your DOM and then we print amicably in the terminal using console.log. Always remember to add a .catch to your promises.

Listing 2. Using the promise.

var hget = require('hget');
var marked = require('marked');
var Term = require('marked-terminal');
 
printRandomArticle();
 
function printRandomArticle () {
  getRandomSiteFooArticle()
    .then(html => hget(html, {
      markdown: true,
      root: 'main',
      ignore: '.at-subscribe,.mm-comments,.de-sidebar'
    }))
    .then(md => marked(md, {
      renderer: new Term()
    }))
    .then(txt => console.log(txt))
    .catch(reason => console.error(reason));
}

This code was "better than using callbacks" when it comes to read the code sequence.

Using generators

We have explored the generators as a way to get the html available in a synthetic and synchronously form. Even if the code is now a bit synchronous, there is a little encapsulation involved, and generators may not be the best way to get the results we want, then we will continue using Promises.

Listing 3. Getting a random article.

function getRandomSiteFooArticle (gen) {
  var g = gen();
  request('https://site.com/articles/random', (err, res, body) => {
    if (err) {
      g.throw(err); return;
    }
    g.next(body);
  });
}
 
getRandomSiteFooArticle(function* printRandomArticle () {
  var html = yield;
  var md = hget(html, {
    markdown: true,
    root: 'main',
    ignore: '.at-subscribe,.mm-comments,.de-sidebar'
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  console.log(txt);
});

Remember that you should encapsulate the call to yield in a try/catch block to preserve the error handling we added when we use promises. We don’t even need to say that the use of generators in this way does not allow us to scale well our applications. Besides involving a non-intuitive syntax in that mix, your iterator code is tightly coupled to the generator being consumed. This makes you to change it every time a new await expression is inserted into the generator. The best alternative is to use a new feature that is coming: Asynchronous functions.

Using async/await

When the asynchronous functions finally arrive, we will be able to pick up our implementation based on promises and take advantage of the style of "synchronous appearance" of generators. Another benefit of this approach is that we will not have to change the getRandomSiteFooArticle while it returns a promise that can be waited.

Realize that await may only be used in functions marked with the async key word. It works similarly to generators, suspending the execution in context until the promise is delivered. If the expected expression is not a promise, it is transformed into a promise.

Listing 4. Transforming a promise.

read();
 
async function read () {
  var html = await getRandomSiteFooArticle();
  var md = hget(html, {
    markdown: true,
    root: 'main',
    ignore: '.at-subscribe,.mm-comments,.de-sidebar'
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  console.log(txt);
}

Again - as well as the generators - remember that you should encapsulate await in a try/catch block so we can catch and handle errors expected from the promises of asynchronous functions.

In addition, an asynchronous function will always return a Promise. This promise is rejected in case of unhandled exceptions or is resolved and sent as return of a asynchronous function, otherwise. This allows us to invoke an asynchronous function and mix it with a continuation based on promises. The following example (Listing 5) shows how the two approaches can be combined.

Listing 5. Combining approaches.

async function asyncFun () {
  var value = await Promise
    .resolve(1)
    .then(x => x * 3)
    .then(x => x + 5)
    .then(x => x / 2);
  return value;
}
asyncFun().then(x => console.log(`x: ${x}`));
// <- 'x: 4'

Returning to our previous example, it shows that we can use the return txt of our async read function and allow "consumers" to continue using promises or even another async function. Thus, our read function should only be concerned with printing a markdown legibly at the terminal of a random article.

Listing 6. Reading and printing an article.

async function read () {
  var html = await getRandomSiteFooArticle();
  var md = hget(html, {
    markdown: true,
    root: 'main',
    ignore: '.at-subscribe,.mm-comments,.de-sidebar'
  });
  var txt = marked(md, {
    renderer: new Term()
  });
  return txt;
}

Then you can add later an await read() function in another async.

async function write () {
  var txt = await read();
  console.log(txt);
}

Or you could simply use promises to give continued.

read().then(txt => console.log(txt));

Fork in the road

In an asynchronous code flow, it is common to perform two or more tasks concurrently. While the asynchronous functions facilitate writing asynchronous code, they also transform themselves into a serial code, i.e., a code that performs one operation at a time. A function with multiple await will be suspended once in every await until reaching the Promise (before resuming the execution and moving to the next await, not unlike what can be seen with the generators and the yield).

To get around this you can use the Promise.all to create a unique promise that you will await for. The only problem is getting the habit of using the Promise.all instead of letting it occur in series, because it can also decrease the performance of your code.

The following example shows how you can use the await in three different promises that could be executed concurrently. Once await suspends its async function and the Promise.all await results in an array, we can deconstruct to pull results individually from the array.

async function concurrent () {
  var [r1, r2, r3] = await Promise.all([p1, p2, p3]);
}

Until some time ago, there was an alternative to the above code: await*, where you did not encapsulate the promises with Promise.all. Babel 5 still supports this syntax, but it was taken from the documentation of Babel 6.

async function concurrent () {
  var [r1, r2, r3] = await* [p1, p2, p3];
}

You can also use something like all = Promise.all.bind(Promise) for an alternative to Promise.all. From this point, you can do the same for Promise.race, which had no equivalent to await*.

Listing 7. Looking at bind.

const all = Promise.all.bind(Promise);
async function concurrent () {
  var [r1, r2, r3] = await all([p1, p2, p3]);
}

Error Handling

Note that errors are swallowed "silently" in Asynchronous functions - as well as in normal Promises. Unless you add try/catch blocks around await calls, uncaught exceptions - regardless of whether it occurred in the body of your Async function or while it was suspended during await - will reject the Promise returned by the async function.

Of course, this can be seen as a strong point: you have the ability to take advantage of the conventions of using try/catch, something that you were unable to accomplish with the use of callbacks - and somehow use with Promises. In this sense, asynchronous functions are similar to generators, where you also had the ability to take advantage of using try/catch function only possible after the execution’s suspension of the function making an asynchronous flow in a synchronous code.

In addition, you are also able to catch exceptions from outside the asynchronous function simply by adding a clause .catch to the Promise it returns. While this is a flexible way to combine the error handling using try/catch with .catch clauses in Promises, it can also lead to great confusion and make mistakes that remain untreated.

read()
  .then(txt => console.log(txt))
  .catch(reason => console.error(reason));

We must be careful and educate ourselves about the different ways in which we can find, treat, and prevent register exceptions.

Using async/await today

One of the ways to use asynchronous functions in your code today is through Babel. This involves a series of modules, but you can always create a module that encapsulates all these others in one if you prefer. I included an npm-run as a useful way to keep everything locally installed packages.

Listing 8. Npm run command.

npm i -g npm-run
npm i -D   browserify   babelify   babel-preset-es2015   babel-preset-stage-3   babel-runtime   babel-plugin-transform-runtime
 
echo '{
  "presets": ["es2015", "stage-3"],
  "plugins": ["transform-runtime"]
}' > .babelrc

The following example will compile the file example.js using the browserify while using babelify to enable support for asynchronous functions. You can then send the script for the node or save to disk.

npm-run browserify -t babelify example.js | node

Further reading

The draft specifications for asynchronous functions is very short and should be an interesting to read if you want to learn more about this functionality.

I glued a piece of code below in order to help you to understand how asynchronous functions work internally. Even if we cannot create new keywords, it is important in terms of understanding what is going on behind the curtains to async/await.

It is useful to know that asynchronous functions internally take advantage of generators and promises.

The following code shows how a statement of an asynchronous function can be transformed into a common function that returns the result of feeding the spawn with a generator - which we will consider await as the equivalent syntax for yield.

Listing 9. Transforming an async function.

async function example (a, b, c) {
  example function body
}
 
function example (a, b, c) {
  return spawn(function* () {
    example function body
  }, this);
}

In spawn, a promise is wrapped around the code that will go through the generator - composed of the user code - in series, passing on values to the "generator" (body asynchronous function). With this, we can see that asynchronous functions is a syntactic sugar using generators and promises, this makes it important for you to understand how each of these parts work so you can have a better understanding of how you can mix, compare and combine different types of asynchronous code streams together.

Listing 10. Spawn example.

function spawn (genF, self) {
  return new Promise(function (resolve, reject) {
    var gen = genF.call(self);
    step(() => gen.next(undefined));
    function step (nextF) {
      var next;
      try {
        next = nextF();
      } catch(e) {
        // finished with failure, reject the promise
        reject(e);
        return;
      }
      if (next.done) {
        // finished with success, resolve the promise
        resolve(next.value);
        return;
      }
      // not finished, chain off the yielded promise and `step` again
      Promise.resolve(next.value).then(
        v => step(() => gen.next(v)),
        e => step(() => gen.throw(e))
      );
    }
  });
}

The pieces of code shown should help you to understand how the algorithm async/ await iterates over a sequence generators (await expressions) by encapsulating each item in sequence on a promise and then chaining to the next sequence. When the sequence ends or one of the promises is rejected or the promise is returned to the function that called the generator.



Fabrí­cio Galdino is a software expert and has worked with IT analysis and business development for more than five years. It has extensive experience with testing, back and front-end technologies.

What did you think of this post?
Services
[Close]
To have full access to this post (or download the associated files) you must have MrBool Credits.

  See the prices for this post in Mr.Bool Credits System below:

Individually – in this case the price for this post is US$ 0,00 (Buy it now)
in this case you will buy only this video by paying the full price with no discount.

Package of 10 credits - in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download few videos. In this plan you will receive a discount of 50% in each video. Subscribe for this package!

Package of 50 credits – in this case the price for this post is US$ 0,00
This subscription is ideal if you want to download several videos. In this plan you will receive a discount of 83% in each video. Subscribe for this package!


> More info about MrBool Credits
[Close]
You must be logged to download.

Click here to login