protips

Logo

This site lists the protips that we shared with students during our courses

View the Project on GitHub appliedtechnology/protips

With the advent of async/await we have several options for handling the asynchronous flow of code in our programs. In this post, I wanted to compare them and give some background and recommendation.

Why asynchronous program flow?

Asynchronous means not occurring at the same time. We need that in software development because some of the things that we do (manipulating files, calling databases or APIs) takes a “long” time.

Imagine, for example, that our browser requests a web page, that takes 2 seconds to load, and does that in a synchronous fashion, locking the main thread that displays the UI. During the entire request and rendering of the page, our browser will be unresponsive; you can’t change tab, you can’t click anywhere or even close the browser window. Pretty annoying, huh?

Now imagine that our server is doing that same request, for two seconds. This is even worse; because now everyone accessing the server will have to wait for one request to finish before the next is served. If 3 people issue the request at the same time the last person has waited 3x2 seconds before a response is returned back.

JavaScripts way to handle this problem is to do asynchronous calls, meaning that the request is done on another thread so that the main thread is freed up to do other things (respond to clicks in the browser and handle another request on the server). Once the request returns it will get onto the main thread again and be returned back to the user.

Options, options, options

With the advent of the async/await keywords we now have three main options to do asynchronous code in JavaScript:

Let’s go through each option with an example

An example

In this example, we are handling a web request from a client (so this is server-side code). We handle the request by writing a file and then returning a response. If the writing fails we want to return an error code to the client.

Implementing the example using callbacks

const express = require('express');
const app = express();
const fs = require('fs');
const uuid = require('uuid/v4');
// ...
// Some other code that is not relevant for this example
// ...

app.post('/api/carts', async (req, res) => {
  const id = uuid();
  fs.writeFile('db/development/carts/' + id, '[]', (error) => {
    if (error) {
      res
        .set('message', 'It failed ' + error)
        .status(401)
        .send();
    }

    res
      .set('location', `/api/carts/${id}`)
      .status(201)
      .send(JSON.stringify({ id: id }));
  });
});

module.exports.app = app;

The start is some basic setup of the Express server etc. The real code starts in the route-handler (app.post('/api/carts', (req, res) => {), on line 9.

Implementing the example using Promises

const express = require('express');
const app = express();
const fs = require('fs');
const uuid = require('uuid/v4');
// ...
// Some other code that is not relevant for this example
// ...
const util = require('util');
const writeFilePromise = util.promisify(fs.writeFile);

app.post('/api/carts', (req, res) => {
  const id = uuid();
  writeFilePromise('db/development/carts/' + id, '[]')
    .then(() => {
    res
      .set('location', `/api/carts/${id}`)
      .status(201)
      .send(JSON.stringify({id : id}));
  })
    .catch(error => {
    res
      .set('message', 'It failed + ', error)
      .status(401)
      .send();
  });
});

module.exports.app = app;

Implementing the example using async/await

const express = require('express');
const app = express();
const fs = require('fs');
const uuid = require('uuid/v4');
// ...
// Some other code that is not relevant for this example
// ...
const util = require('util');
const writeFilePromise = util.promisify(fs.writeFile);

app.post('/api/carts', async (req, res) => {
  const id = uuid();
  try {
    const id = uuid();
    await writeFilePromise('db/development/carts/' + id, '[]');

    res
      .set('location', `/api/carts/${id}`)
      .status(201)
      .send(JSON.stringify({ id: id }));
  } catch (error) {
    res
      .set('message', 'It failed + ', error)
      .status(401)
      .send();
  }
});

module.exports.app = app;

The async/await version is fun since you can’t really tell that the code is asynchronous by just looking at it. async/await lets us write asynchronous code as if it was synchronous.

Comparision

Flow

First, let’s see how the asynchronous flow is handled by different approaches:

Error handling

Errors are handled a bit differently per approach

Pros and cons

This section will, of course, contain some personal (Marcus) opinions but might still be useful.

Summary - what should I choose then?

Use the most advanced option you have to your disposal

But most importantly - ensure that you understand how asynchronous code work under whatever solution you decide to use.