DEV Community

Cover image for Promises and Async/Await: Two Sides of the Same Coin 🚀
Ali Samir
Ali Samir

Posted on

Promises and Async/Await: Two Sides of the Same Coin 🚀

Imagine this: You’re a chef juggling multiple dishes in the kitchen. You’ve got timers set for boiling water, baking a cake, and roasting chicken. Now, how cool would it be if you could get a notification instead of constantly checking on each dish when each is done?

That’s exactly what JavaScript Promises and async/await do for your code—they help you manage tasks that take time (asynchronous operations) without driving you crazy.

But why do we need two tools for the same job? Let’s dive in to understand how Promises and async/await are connected, why async/await was introduced, and how it improves upon promises.


đź“Ť What Are Promises?

Promises in JavaScript are like IOUs. When you call an asynchronous function, it returns a promise, which is a placeholder for the result of that operation—something that’s not available yet but will be resolved (or rejected) in the future.

Here’s an example:

const boilWater = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve('Water boiled!');
    }, 2000);
  });
};

boilWater()
  .then((result) => console.log(result)) // Logs: "Water boiled!"
  .catch((error) => console.error(error));
Enter fullscreen mode Exit fullscreen mode

Promises let you write cleaner code than old-school callback functions (remember those callback pyramids of doom?). With .then() and .catch(), you can chain actions and handle errors more gracefully.

However, promises have some shortcomings.


đź“Ť Shortcomings of Promises

1- Nested Hell: Even though promises save us from callback pyramids, chaining .then() calls can still get messy if you’re dealing with complex logic.

boilWater()
  .then(() => brewTea())
  .then(() => pourTea())
  .then(() => console.log('Tea is ready!'))
  .catch((error) => console.error(error));
Enter fullscreen mode Exit fullscreen mode

This can become difficult to read and debug as the chain grows longer.

2- Error Handling: If you forget a .catch() at the end of a promise chain, unhandled errors might sneak into your application, leading to unexpected behavior.

3- Synchronous Illusion: While promises simplify asynchronous programming, they don’t feel intuitive to developers used to writing synchronous code.


đź“Ť Enter Async/Await

JavaScript introduced async/await in ES2017 (ES8) to address these issues and make asynchronous code look and behave more like synchronous code.

How Does Async/Await Work?

At its core, async/await is syntactic sugar built on top of promises. Here’s how it works:

  • Mark a function with async to indicate that it performs asynchronous operations.

  • Use await inside the function to pause execution until a promise is resolved.

Let’s rewrite the tea-making example using async/await:

const makeTea = async () => {
  try {
    await boilWater();
    await brewTea();
    await pourTea();
    console.log('Tea is ready!');
  } catch (error) {
    console.error(error);
  }
};

makeTea();
Enter fullscreen mode Exit fullscreen mode

Notice how this code feels more linear and readable, almost like synchronous code? That’s the magic of async/await.


✨ Why Was Async/Await Introduced?

  • Improved Readability: With async/await, asynchronous code flows from top to bottom, mimicking the structure of synchronous code. This makes it easier for developers to follow the logic.

  • Easier Debugging: Debugging promises can be tricky since errors often bubble up through chained .then() calls. With async/await, errors can be caught using standard try/catch blocks, making debugging a breeze.

  • Reducing Boilerplate: Promise chains often involve a lot of .then() calls. async/await eliminates this boilerplate, leading to cleaner code.

  • Consistency: In complex scenarios, combining multiple asynchronous operations using promises can lead to tricky syntax. async/await simplifies handling parallel operations (using Promise.all) and sequencing.


Promises vs Async/Await âś…

Here’s a side-by-side comparison to highlight their differences:

Feature Promises Async/Await
Syntax Chain-based using .then() and .catch() Linear, similar to synchronous code
Readability Can get messy with long chains Cleaner and easier to follow
Error Handling Requires .catch() for errors Uses try/catch blocks for better handling
Debugging Harder to trace errors in chains Easier to debug due to synchronous-like flow

Final Thoughts đź’Ż

Promises laid the foundation for modern asynchronous programming in JavaScript, but async/await elevated it to a whole new level of simplicity and readability.

Both are invaluable tools, and you’ll often find yourself using them together—for example, awaiting a Promise.all() or working with APIs that still return promises.

So, the next time you’re faced with an asynchronous task, remember: promises are the backbone, but async/await is the elegant outfit that makes them shine.

Happy coding!

Top comments (0)