DEV Community

Cover image for How to Write Asynchronous JavaScript Code
Charles Clinton Pustejovsky III
Charles Clinton Pustejovsky III

Posted on • Updated on

How to Write Asynchronous JavaScript Code

How to Write Asynchronous Code in NodeJS

JavaScript is a non-blocking, single-threaded programming language. It will not go from top to bottom, running your functions one line at a time like you would expect.

For example, here is some simple code to read a file:

const fs = require("fs");

console.log("starting");
fs.readFile("/path/to/helloworld.txt", "utf8", (err, data) => {
  if (err) console.log(err);
  console.log(data);
});
console.log("finishing");
Enter fullscreen mode Exit fullscreen mode

You may expect the result to be

starting
<file contents>
finishing
Enter fullscreen mode Exit fullscreen mode

But instead you get:

starting
finishing
<file contents>
Enter fullscreen mode Exit fullscreen mode

This is because JavaScript does not stop. It will keep going down your code while waiting for a process to finish. There are three ways to handle this and I'll go over them from worst to best.

The Humble Callback

To use callbacks for this code, you would do the following:

console.log("starting");
fs.readFile("/path/to/helloworld.txt", "utf8", (err, data) => {
  if (err) {
    console.log(err);
    return; //or throw(err) or something else to strop the function
  } else {
    console.log(data);
    console.log("finishing");
  }
});

Enter fullscreen mode Exit fullscreen mode

**Note: be sure to add a return after an error message and to use if/else to make sure the function does not continue if there is a problem.

Promises

You have to keep nesting callback functions within callback functions which can lead to deeply-nested code that is hard to read, better known as callback hell.

Promises are wonderful additions to JavaScript to rescue JavaScript developers from callback hell.

You can read more about Promises in depth and how to create them on MDN, but here is an example of how to consume them. Most APIs will have some way to use their code as a promise whether it is NodeJS's util.promisify or AWS's .promise() method for most of their API. For this example, we'll use promisify:

const fs = require("fs");
const { promisify } = require("util");
const ReadFilePromise = promisify(fs.readFile);

console.log("starting");
ReadFilePromise("/path/to/helloworld.txt", "utf8")
  .then((data) => console.log(data))
  .catch((err) => console.log(err))
  .finally(() => console.log("finishing"));
Enter fullscreen mode Exit fullscreen mode

You add a .then() for the data, a .catch() for the error, and a .finally() for anything you want to do after the data or error is returned.

Async/Await

Finally we'll come to my favorite way to write JavaScript code, async/await. The async keyword is syntactic sugar that allows a function to return a Promise. So for this example we can use the same ReadFilePromise from the last example. We'll need to wrap this logic inside an async function and call it:

const ReadFileAsync = async(path) => {
  console.log("starting");
  try {
    let data = await ReadFilePromise(path)
    console.log(data)
  } catch (error) {
    console.log(error)
  }
  console.log("finishing")
}

ReadFileAsync("/path/to/helloworld.txt", "utf8")
Enter fullscreen mode Exit fullscreen mode

NOTE: adding async to a function using callbacks does not make it work asynchronously. The function will still be using callback, but JavaScript now thinks it will return a Promise.

You want to wrap your await inside a try/catch to allow for error handling. Speaking of error handling...

How To Do Error Handling

To make sure your function bubbles the error up to the code using your function, throw it!

Let's my ReadFileAsync a function that another function can use.

const ReadFileAsync = async (path) => {
  console.log("starting");
  try {
    return await ReadFilePromise(path);
  } catch (error) {
    throw error;
  }
};

async function main() {
  try {
    let data = await ReadFileAsync("/path/to/helloworld.txt", "utf8");
    console.log(data);
  } catch (error) {
    console.log(error);
  } finally {
    console.log("finishing");
  }
}

main()
Enter fullscreen mode Exit fullscreen mode

Top comments (0)