Skip to main content

Promises and async/await

JavaScript, traditionally, is "single-threaded". This means that it can only do one thing at a time.

If you ask JavaScript to download a heavy 500MB file from the internet synchronously (normally), the entire web page would "freeze" completely until the file finishes downloading. You wouldn't be able to click buttons or scroll.

To avoid this, JavaScript uses asynchrony. It tells the browser: "Go download this file in the background. I will continue executing the rest of my code. Let me know when you finish".

Promises (Promises)

A Promise is an object that represents the eventual completion or failure of an asynchronous operation.

Think of a promise like ordering a burger in a restaurant:

  1. You pay and they give you a ticket (The promise). The burger does not exist yet (Pending state).
  2. You go sit down and continue talking with your friends (JavaScript continues executing other code).
  3. Eventuality A (Success): Your number is called and they hand you the food. The promise is resolved (Fulfilled).
  4. Eventuality B (Error): The cook comes out and tells you they ran out of meat. The promise failed (Rejected).
// Simulation of a promise (normally these are given to you by external APIs, you don't create them by hand that often)
const myOrder = new Promise((resolve, reject) => {
let success = true;

setTimeout(() => { // Simulating that it takes 2 seconds to prepare the order
if (success) {
resolve("Cheeseburger delivered"); // Success!
} else {
reject("Error: We ran out of bread"); // Failure!
}
}, 2000);
});

// HOW TO CONSUME THE PROMISE? (The old way with .then)
myOrder
.then((food) => {
// Uniquely executes ONLY IF it was resolve()
console.log("Yummy!", food);
})
.catch((error) => {
// Uniquely executes ONLY IF it was reject()
console.error("Oh no:", error);
});

⭐️ The Modern Way: async / await

Although .then() and .catch() work well, when you have many nested promises (promise inside promise inside promise) the code becomes very difficult to read.

In 2017 the keywords async and await were introduced, built on top of Promises, which allow us to write asynchronous code that reads almost exactly like normal synchronous code.

1. async (The asynchronous function)

Placing the word async before a function means two things:

  • That function automatically returns a Promise always.
  • It activates the permission to use the magic word await inside it. (You cannot use await in a normal function).

2. await (Pause and wait)

It causes the execution of THAT SAME ASYNCHRONOUS FUNCTION (nothing else) to temporarily pause until the Promise to its right is resolved or fails.

// Using the same "myOrder" promise from the previous example
const goToRestaurant = async () => {
console.log("1. Arriving at the restaurant...");
console.log("2. Ordering...");

try {
// Instead of using .then, here we "pause" until we have the result.
const food = await myOrder;

console.log("3. Enjoying the", food); // Only 2 seconds later
} catch (error) {
// Instead of using .catch
console.error("3. There was a problem:", error);
}
};

goToRestaurant();
console.log("--- The outside world keeps turning ---");

/* EXPECTED OUTPUT:
1. Arriving at the restaurant...
2. Ordering...
--- The outside world keeps turning ---
(2 seconds of pause in the asynchronous function...)
3. Enjoying the Cheeseburger delivered
*/