Callback, Promise and Async/Await by Example in JavaScript

dadepo

dade!

Posted on May 20, 2021

Callback, Promise and Async/Await by Example in JavaScript

This post is going to show, by way of code examples, how to take a callback based API, modify it to use Promises and then use the Async/Await syntax. This post won't go into a detailed explanation of callbacks, promises or the Async/Await syntax. For such a detailed explanation of these concepts please check Asynchronous JavaScript, which is a section of MDN Web Docs, that explains asynchronicity and how callbacks, promises, and the Async/Await syntax helps with working with asynchronous JavaScript.

This post is meant for the developer who has somewhat of an understanding of asynchronicity in JavaScript, but requires a straight to the point code example to serve as a quick syntax reference for how to take a callback based API, update it to use promises and finally use the Async/Await with it.

For demonstration purposes, we are going to use the fs.readFile, which is a callback based API from reading files. We will have a file test.txt that would contain some text, we will then have a file script.js that would open the file, read the contents, and print it to the terminal.

The code will first be implemented using callbacks, it would then be updated to use Promises, and finally, instead of using Promise directly, it will be updated to use Async/Await.

Let's get started.

Using Callbacks

We first create a directory where we are going to work from, create also the file that will contain our code, and the two files that we would be reading from.

We first create the two files with contents.

$ mkdir ~/code
$ touch ~/code/script.js
$ echo "Beam me up, Scotty" > ~/code/test.txt
$ cd ~/code/
Enter fullscreen mode Exit fullscreen mode

Next in the script.js file, we have the following code:

const fs = require("fs")

function readFileCallBack() {

fs.readFile("./test.txt", 'utf8',  (err, data) => {
  if (err) {
     console.error(err)
     return
   }
   console.log(data.trim() + " [callback]")
  })

}

readFileCallBack()
Enter fullscreen mode Exit fullscreen mode

Executing the script by running node script.js should get "Beam me up, Scotty" printed to the terminal:

$ node script.js
Beam me up, Scotty [callback]
Enter fullscreen mode Exit fullscreen mode

Using Promises

Update script.js and add a version of readFileCallback that uses promises. It looks like this:


function readFilePromise() {
  return new Promise((resolve, reject) => {
     fs.readFile("./test.txt", 'utf8',  (err, data) => {
     if (err) {
       reject(err)
       return
     }

      resolve(data.trim())
    })
  });
}

readFilePromise()
 .then(data => console.log(data  + " [promise]"))
 .catch(err => console.log(err))
Enter fullscreen mode Exit fullscreen mode

Execute the script by running node script.js:

$ node script.js
Beam me up, Scotty [callback]
Beam me up, Scotty [promise]
Enter fullscreen mode Exit fullscreen mode

Using Async/Await

Update script.js and add a third version that uses the Async/Await syntax. Since Async/Await is a syntax that makes using promises easier, the Async/Await implementation would make use of the readFilePromise() function. It looks like this:

async function readFileAsync() {
  try {
    const data = await readFilePromise()
    console.log(data.trim() + " [async-await]")
  } catch (err) {
    console.log(err)
  }
}

readFileAsync()
Enter fullscreen mode Exit fullscreen mode

Executing the script by running node script.js will print something similar to this, to the terminal:

Beam me up, Scotty [callback]
Beam me up, Scotty [promise]
Beam me up, Scotty [async-await]
Enter fullscreen mode Exit fullscreen mode

The complete file with the 3 implementations is presented below:

const fs = require("fs")

// callback
function readFileCallBack() {

fs.readFile("./test.txt", 'utf8',  (err, data) => {
  if (err) {
    console.error(err)
    return
  }
  console.log(data.trim() + " [callback]")

 })

}

readFileCallBack()

// promise
function readFilePromise() {
  return new Promise((resolve, reject) => {
     fs.readFile("./test.txt", 'utf8',  (err, data) => {
     if (err) {
       reject(err)
       return
     }

      resolve(data.trim())
    })
  });
}


readFilePromise()
 .then(data => console.log(data  + " [promise]"))
 .catch(err => console.log(err))


// async/await
async function readFileAsync() {
  try {
    const data = await readFilePromise()
    console.log(data.trim() + " [async-await]")
  } catch (err) {
    console.log(err)
  }
}

readFileAsync()
Enter fullscreen mode Exit fullscreen mode

Error Handling

To illustrate that the error handling in the 3 implementation work as expected, rename the test.txt file and rerun the script:

$ mv test.txt test.txt.backup
$ node script.js
[Error: ENOENT: no such file or directory, open './test.txt'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: './test.txt'
}
[Error: ENOENT: no such file or directory, open './test.txt'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: './test.txt'
}
[Error: ENOENT: no such file or directory, open './test.txt'] {
  errno: -2,
  code: 'ENOENT',
  syscall: 'open',
  path: './test.txt'
}
Enter fullscreen mode Exit fullscreen mode

Showing that the error handling code, which is to just print the error to the console, works as expected in the 3 implementations.

💖 💪 🙅 🚩
dadepo
dade!

Posted on May 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related