Asynchronous JavaScript - what is it? (Promises, callbacks, async/await)

Omar Benseddik's photo
Omar Benseddik
Updated 2023-11-13 路 4 min
Table of contents

JavaScript code is executed synchronously. In other words, from top to bottom and one line at a time.

function getText() {
return "Hi 馃憢, I'm Tinloof";
}
let text = getText();
console.log(text);
// Output:
// Hi 馃憢, I'm Tinloof

First, the code will execute the function and know what to return when getText() is called.

Then it assigns the getText() function to the variable text.

Finally, it logs the variable text to the console, and the output is "Hi 馃憢 , I'm Tinloof".

So far, this works great and we're facing no obstacle.

Now, imagine we have to make a network request to get the text "Hi 馃憢 , I'm Tinloof" and the user doesn't have a fast Internet connection.

// Assume getTextFromServer is making a network request to get data
let text = getTextFromServer();
// 馃暟 Wait until we receive the text from the server
// 馃崷 Meanwhile the page is frozen and the user can't interact with it
console.log(text);
// Output:
// Hi 馃憢, I'm Tinloof

The code works, but while we wait for the text from the server, our page freezes.

One appraoch to solve this problem is called "callbacks".

Callback functions

getTextFromServer((error, text) => {
if (error) {
console.log("Error getting the text:", error);
} else {
console.log(text);
}
});
// Output (if we successfully get the text from the server)
// Hi 馃憢 , I'm Tinloof
// Output (if we are not successfully getting the text from the server)
// Error getting the text: some error from the server.

Instead of waiting for getTextFromServer() to finish, we let it run in the background and pass to it a function, called "callback function" or "callback", to handle the result of the call once it's done.

To handle the scenario where the request fails or the one where it succeeds, our callback function takes 2 parameters:

  1. An error, which is empty if the request is successful
  2. A result (in our case it's the text "Hi 馃憢 , I'm Tinloof")
Notice: If you're not familiar with arrow functions, here's a link to the MDN documentation.

We just wrote our first asynchronous code!

Guess what? We just learned one approach of writing asynchronous code in JavaScript.

In the example above, while our code is looking to get the text from the server, the rest of our code would still run.

Another approach to asynchronous code is called Promises.

Promise

let promise = getTextFromServer();
promise
.then((text) => {
console.log(text);
})
.catch((error) => {
console.log("Error getting the text:", error);
});
// Output (if we successfully get the text from the server)
// Hi 馃憢 , I'm Tinloof
// Output (if we are not successfully getting the text from the server)
// Error getting the text: some error from the server.

Instead of accepting a callback, getTextFromServer() returns a Promise object.

A Promise is an object that gives us the result of the success of an asynchronous operation or the result of its failure (it either resolves or rejects).

It does that by providing a then() function to handle success and catch() to handle errors.

JavaScript has a syntatic sugar (jargon for "more beautiful syntax") for Promises, let's check it out.

async/await

try {
let text = await getTextFromServer();
console.log(text);
} catch (error) {
console.log("Error getting the text:", error);
}
// Output (if we successfully get the text from the server)
// Hi 馃憢 , I'm Tinloof
// Output (if we are not successfully getting the text from the server)
// Error getting the text: some error from the server.

Instead of using the Promise syntax, which can be confusing at times, we simply await getTextFromServer() using the await keyword.

To handle the error and success scenarios, we surround our code in a try/catch block.

If the request is successful, the try block will be executed to its end and the text will be printed.

If the request fails, we'll jump directly from the await line to the catch block and output the error.

Using "await" in a function

If we want to use the await syntax in a function, the function has to be declared with the async keyword.

async function getText() {
try {
let text = await getTextFromServer();
console.log(text);
} catch (error) {
console.log("Error getting the text:", error);
}
}
console.log(getText);
// Output (if we successfully get the text from the server)
// Hi 馃憢 , I'm Tinloof
// Output (if we are not successfully getting the text from the server)
// Error getting the text: some error from the server.

Conclusion

We now know what Asynchronous JavaScript is, and learned how to write it with 3 approaches:

  • Callback functions
  • Promises
  • async...await (which is just a prettier syntax of Promises)

Believe it or not, if you made it so far while understanding everything, you can build most features that require asynchronous code.

Though, we strongly advise you to dig deeper in the topic. Here are a few resources:

What the heck is the event loop anyway? by Philip Roberts

Asynchronous programming by Axel Rauschmayer

Rethinking Asynchronous JavaScript by Kyle Simpson on Frontend Masters. You can also read the book YDKJS for free here

Async + Await in JavaScript by Wes Bos

Async-Await cheatsheet from CodeCademy

Recent articles