Explore the future of Web Scraping. Request a free invite to ScrapeCon 2024

A Guide to Nested Promises, Anti-Pattern, and Alternatives

image

Promise in JavaScript is a popular design pattern used to handle asynchronous tasks in Node.js. It can take care of parallel/serial execution of many tasks along with their chaining rules.

In my previous article, we learned the basics of Promise along with common Promise utility methods.

In this post, we are going to learn about nested promises and alternatives to them. Without wasting any more time, let’s jump into nested promise.

Nested Promise:

Promises give you return statements and error throwing, which you lose with continuation-passing style. A nested promise is when you call child promise inside .then of parent promise and this go-on.

Let’s take an example, a developer wants to first create a user account, followed by subscription information, followed by subscription purchase history. This will look something like this:

return promise1.then((promise1_output) => {
  return promise2.then((promise2_output) => {
    return promise3.then((promise3_output) => {
      console.log(promise3_output); // this continue
    });
  });
});

In the above code, promise2 executes only if promise1 resolves & promise3 is executed only if promise2 resolved.

In a promise nesting when you return a promise inside a then method, and if the returned promise is already resolved/rejected, it will immediately call the subsequent then/catch method, if not it will wait.

If promised is not return, it will execute parallelly.

These nested promises will have access to variables from their outer scope and are otherwise called “promise-pyramids”, which will grow to the right.

If not done correctly, this promise pyramid can eventually lead to something called as Promise-Callback Hell.

During nesting, if you forget to return the nested promises, it will lead to an anti-pattern where your resolves/rejects will not bubble up to the top.

The core things that can make promises confusing is not properly documenting return values, and not explicitly blocking for promise resolution with things like Promise.all. With a chain of promises, you should consider .finally to clean up the logic in case anything goes wrong.

Sometimes, nesting promises becomes inevitable. This will inevitably lead to the callback hell we talked about, hence to tackle this issue, ES7 has come up with Async/await.

Alternative to Nested Promise:

Async/Await is the extension of promises which we get as support in the language.

This feature basically acts as syntactic sugar on top of promises, making asynchronous code easier to write and to read afterward. They make async code look more like old-school synchronous code.

async keyword, which you put in front of a function declaration to turn it into an async function. Invoking the async function returns a promise. This is one of the traits of async functions — their return values are guaranteed to be converted to promises.

The advantage of an async function only becomes apparent when you combine it with the await keyword. await only works inside async functions within regular JavaScript code.

await is put in front of any async promise-based function to pause your code on that line until the promise fulfills, then return the resulting value.

You can use await when calling any function that returns a Promise, including web API functions.

Let’s rewrite the nested promise using async-await

async function userInfo() {
  const promise1 = await new Promise((resolve, reject) => {
    resolve("USer account created!");
  });
  if (promise1) {
    const promise2 = await new Promise((resolve, reject) => {
      resolve("Subscription created!");
    });
    if (promise2) {
      const promise3 = await new Promise((resolve, reject) => {
        resolve("Subscription history created!");
      });
    }
  }
}

Instead of needing to chain a .then() block on to the end of each promise-based method, you just need to add an await keyword before the method call, and then assign the result to a variable.

The await keyword causes the JavaScript runtime to pause execution of further code on this line until the async function call has returned its result — very useful if subsequent code relies on that result!

For error handling, you can use synchronous try...catch structure with async/await.

    async function userInfo() {
     try {
      const promise1 = await new Promise((resolve, reject) => {
       resolve('USer account created!');
      });
      ...
     } catch (err) {
      console.log(err)
     }
    }

async/await is built on top of promises, so it’s compatible with all the features offered by promises. This includes Promise.all() — you can await a Promise.all() call to get all the results returned into a variable in a way that looks like simple synchronous code.

async function promiseAllDemo() {
  try {
    const promise1 = Promise.resolve("hello world");
    const promise2 = "Promise 2";
    const promise3 = new Promise((resolve, reject) => {
      setTimeout(resolve, 100, "foo");
    });
    const resolvedPromises = await Promise.all([promise1, promise2, promise3]);
  } catch (err) {
    console.log(err);
  }
}

Conclusion:

Async/await provides a nice, simplified way to write async code that is simpler to read and maintain.




Continue Learning