Node.js Promises vs Async/Await: A Detailed Analysis

Node.js is an open-source cross-platform JavaScript runtime built on Chrome’s V8 JavaScript engine that uses the process model instead of the traditional web server model making it super useful for web development. 

It allows developers to use JavaScript to create server-side applications that can handle multiple requests simultaneously without any clash between them that’s why it is preferred for building real-time, data-intensive applications.

With that ability to handle multiple requests simultaneously, we need to have some handler to control the async execution, here comes the two very important tools: Promises and Async/Await.

In this article, we will understand these constructs in detail. Also, we will compare both and conclude which one is more suitable. But before we get started, let’s take a quick look at synchronous and asynchronous programming to understand the context.

Introduction to Synchronous and Asynchronous Programming in Node.js

In synchronous programming, the statements are executed in a linear order. Only a single statement is executed at a time. i.e., if a statement takes a long time to complete it will block the entire flow of the code. e.g., if we use the “window.alert()” method in JavaScript it will block the rest of the code until a key is pressed.

On the other hand, asynchronous programming is a technique that allows us to execute the rest of the code even if one of the statements is still not executed. In asynchronous programming, the code is not executed in linear order. It allows the program to handle multiple operations simultaneously which makes it more efficient. e.g., if we use the “setTimeInterval()” method in a JavaScript code, it will get executed when the condition is satisfied but it will not block the flow of the code. The rest of the statements will still get executed.

Why Do Promises and Async/Await Matter in Node.js?

Node.js promotes asynchronous programming over synchronous programming. In synchronous programming, it is very easy to handle the operation because when it is completed it gets executed automatically and the rest of the operations wait till it is in process. However, with asynchronous programming, it is quite complex to handle the operation because other operations are dependent on it. It allows other parts to run instead of stopping them and letting the async operation run when the operation is complete. 

So it needs to be managed so that the operation that needs to be done after that async operation has to wait until its execution. Here comes Node.js Promises and Async/Await to control the execution.

Using Promises and Async/Await helps us to manage asynchronous operations easily, write cleaner code, handle errors using try…catch blocks, and many more things become easier with these constructs.

Promises in Node.js

Promises in Node.js are used to bring asynchronous operations to life. With the help of promises, we can handle long-running operations without blocking the main body execution. A promise represents a value that may not be available at the time of execution but in the future, it can have three possible states.

  • Fulfilled
  • Rejected
  • Pending

Promises are like real-life promises. e.g., I can give a promise that I will write the best article on “Node.js Promises and Async/Await”, Now either I will fulfil my promise by writing the best article or I will not write the best article or maybe I will still be writing the article I claimed to be best.

The same way Node.js Promises works. Either they are fulfilled, rejected, or pending.

Syntax:

const myPromise = new Promise((resolve, reject) => {
  if (/* Success  */) {
    resolve(/* Result */);
  } else {
    reject(/* Error */);
  }
});

Handling Success and Errors with Promises

While working with promises we use the “.then()” method to handle the successful completion of the promise whereas the “.catch()” method is used to handle the errors. Promises provide a very flexible handy way to manage asynchronous operations in Node.js which makes it really easy to write manageable and scalable code.

myPromise.then((result) => {
  // Handle the successful execution of the promise with the result
}).catch((error) => {
  // Handle errors that occurred 
});

Why Do We Need Async/Await if We Have Promises?

Promises are really a great and flexible way to handle asynchronous operations but have some flaws due to which the developer has to introduce Async/Await on top of Promises and this started a battle of Async/Await vs Promises.

JavaScript Promises have the following drawbacks:

1. Callback Hell

Though Promises reduce the level of callback function nesting to a large extent, it still leads to a chain of “.then()” calls which are hard to manage. This often occurs when we are dealing with highly complex and big asynchronous workflows.

doSomething()
  .then(function(result1) {
    doSomethingElse(result1)
      .then(function(result2) {
        doAnotherThing(result2)
          .then(function(result3) {
            console.log(result3);
          })
          .catch(function(error) {
            console.error(error);
          });
      })
      .catch(function(error) {
        console.error(error);
      });
  })
  .catch(function(error) {
    console.error(error);
  });

The above code syntax contains a lot of callbacks one inside the other, showing an example of “callback hell” or “pyramid of doom”.

2. Wasted Resources

While working with promises once a promise is invoked it cannot be cancelled. i.e., if we have a long-running operation that we don’t require anymore we cannot cancel its execution. This makes it difficult to manage big and complex workflows and leads to the wastage of resources.

3. Error Handling

Promises are great while working with asynchronous operations, but it doesn’t solve all the problems. It doesn’t have a proper mechanism for handling errors that occur during the execution.

4. Difficult to Debug

While working with promises if a promise throws an error it’s really hard to figure out from where the error originated. This happens particularly when an error is thrown by a complex chain of promises.

5. Adds unnecessary complexity

Promises are great when we are working with long-running complex asynchronous operations but when it comes to working with simple code promises simply add unnecessary complexity to the code.

Async/Await in Node.js

Async/Await is another way of handling asynchronous operations in Node.js. It makes it easier to write asynchronous code in Node.js. It makes the asynchronous code look like synchronous code which makes it easier to read and maintain.

While working with Async/Await, the asynchronous functions are marked with the “async” keyword. These functions return a “Promise object” which is resolved with the “await” keyword.

Syntax:

async function fetchData() {
  const response = await fetch('https://api.example.com/data');
  const data = await response.json();
  return data;
}

In this code, the “fetchData” function is an “asynchronous” function that returns a Promise object. Inside the function, the “fetch” method is used to make a network request to an API. The “await” keyword is used to wait for the response to be returned before continuing. Once the response is returned, the “JSON” method is called on the response object, again using the await keyword to wait for the parsing of the response body to be completed.

Async/Await is a powerful feature that can make asynchronous code easier to write, read, and maintain. It can also make it easier to handle errors in asynchronous code

Difference Between Promises and Async/Await

Promises and Async/Await, both are techniques to handle asynchronous operations in Node.js. There is no particular answer as to which one is better. The choice between the two techniques largely depends on use cases and personal preferences. 

Yet Async/Await is a fairly new technology and it works well with both simple and complex JavaScript code and is really easy to read and maintain which makes the new generation of developers prefer using it over Promises. 

Let’s see the difference between Promises and Async/Await:

1. Readability

While working with Async/Await we can give a synchronous-like look to our asynchronous code that makes the code easier to read, write and maintain. Async/Await also helps to avoid the “Pyramid of doom” and “Callback Hell” which often occurs due to complex promise chains.

2. Chaining

With Promises, we can use .then() and .catch() for chaining but with Async/Await we can use more linear, sequential-looking code because the rest of the code will not execute until the awaited promise is resolved.

3. Error Handling

While using Promises, error handling can be done using .catch() but with Async/Await we can use try/catch blocks providing a more synchronous structure.

4. Compatibility

Promises have been around for a long time, introduced in ES6 so are widely supported in all modern JavaScript environments compared to Async/Await, introduced in ES8 which is relatively new and is not supported in older environments.

5. Flexibility

Async/Await works with both synchronous and asynchronous code which makes it relatively more flexible than Promises which is precisely for handling asynchronous code although it can handle synchronous code.

6. Debugging

Async/Await offers better debugging capabilities. We can use the debugger to check the code line by line as we do in asynchronous programming.

7. Ease of Use

In Promise, we use the concept of chaining that may be difficult to understand for beginners whereas with Async/Await understanding the flow of execution is super easy. It produces code that looks like synchronous styles and is therefore favoured by people who are already familiar with synchronous programming.

8. Return Value

Promises and Async/Await both return a promise. But with Promises, we can chain multiple operations using .then(), on the other hand, with Async/Await, the return value inside the async function is treated as if it were wrapped in Promise.resolve() that’s why we can handle everything inside the function that makes the overall code easier to read and understand.

Summary

All modern web apps that use JavaScript are undone without asynchronous programming. In synchronous programming, only a single statement is executed at a time whereas in asynchronous programming the rest of the code continues being executed even if one of the statements is still in progress which makes it best suited for operations that require waiting time like fetching data from APIs, sending or handling requests simultaneously, etc.

Node.js Promises and Async/Await are the two most important tools that make this asynchronous programming possible.

In this tutorial, we gave a brief introduction to synchronous programming, asynchronous programming, Promises, and Async/Await. We have also covered the benefits of Async/Await compared to Promises in Node.js and the differences between the two. 

After reading this article, you may consider using Async/Await, but later when you step into development you will realize that Promises are sometimes not replaceable and the only solution for some specific problems.

Reference

https://developer.mozilla.org/en-US/docs/Learn/JavaScript/Asynchronous

Vikas Negi
Vikas Negi
Articles: 3