Why JavaScript Has Both Promises and Async/Await for Async Tasks

In JavaScript, we can use both promises and async/await to handle asynchronous tasks. But you might wonder, what’s the point of having two ways to do the same thing?

First, understand that async/await is simply syntactic sugar over promises' .then() and .catch() methods. What is syntactic sugar? It means that when we use async/await, JavaScript is still using .then() and .catch() behind the scenes.

So, essentially, they’re the same thing.

However, when handling multiple promises, the code can quickly become messy. Often, each new promise must be nested within the previous promise's .then() function, leading to hard-to-manage code. You may also end up with multiple .catch() functions, each handling different errors.

fetchDataFromAPI()
  .then((data) => {
    console.log("Data received:", data);
    return processData(data);
  })
  .then((processedData) => {
    console.log("Data processed:", processedData);
    return saveData(processedData);
  })
  .then((savedData) => {
    console.log("Data saved:", savedData);
    return notifyUser(savedData);
  })
  .catch((error) => {
    console.error("Error occurred in the data flow:", error);
  });

Async/await was designed to make working with promises cleaner. Instead of nesting promises, async/await allows you to handle asynchronous operations in a more readable, synchronous-like style.

With await, you simply pause execution until the promise resolves, then move on, handling any errors in a straightforward try...catch block. When we use await, we don’t need callbacks, and we can avoid promise chaining. It’s easier to read and makes more sense logically.

async function handleDataFlow() {
  try {
    const data = await fetchDataFromAPI();
    console.log("Data received:", data);

    const processedData = await processData(data);
    console.log("Data processed:", processedData);

    const savedData = await saveData(processedData);
    console.log("Data saved:", savedData);

    const notification = await notifyUser(savedData);
    console.log("User notified:", notification);
  } catch (error) {
    console.error("Error occurred in the data flow:", error);
  }
}

handleDataFlow();

Ultimately, it’s a personal choice. Use whichever approach you feel most comfortable with.

I have created a set of resources for learning asynchronous JavaScript. These guides—Callbacks, Promises, and Async/Await —cover everything I’ve learned from years of real-world JavaScript experience.

If you found this article helpful, you’ll get so much out of these guides. Each one is optimized for those “lightbulb moments,” building a strong mental model for how asynchronous JavaScript works and how you can use it to create fast, dynamic applications.