DEV Community

druchan
druchan

Posted on

Safe, simple, sequential promises in Javascript

Whenever you need to run a "sequence" of promises one after the other – because, say, you need the output of one promise to be fed into the next promise – things will get a little tricky.

The bad way of doing it would be to chain them.

promise1()
  .then(result1 => {
    promise2(result1)
      .then(result2 => {
        promise3(result2)
          .then(result3 => {
            ...
          })
          .catch(...)
      })
      .catch(...)
  })
  .catch(...)
Enter fullscreen mode Exit fullscreen mode

A better way would be to use the async/await syntactic sugar:

try {
  const result1 = await promise1()
  const result2 = await promise2(result1)
  const result3 = await promise3(result2)
  ...
  return resultFinal
} catch (e) {
  // do something with error e
}
Enter fullscreen mode Exit fullscreen mode

And that's not too bad if you had just 2-3 promises, which I guess would be typical use-case.

However, we could combine the ideas of piping and converting errors to values to build an asynchronous variation of a pipe.

const runAsyncHelper = async (fn, input) => {
  if (input.err) return { err: input.err };
  try {
    const res = { data: await fn(input.data) };
    return res;
  } catch (err) {
    return { err };
  }
};

const pipePromises =
  (...fns) =>
  async (init) => {
    return fns.reduce(
      async (acc, fn) => {
        return await runAsyncHelper(fn, await acc);
      },
      { data: init }
    );
  };
Enter fullscreen mode Exit fullscreen mode

The idea is simple.

  • You take a bunch of promise functions
  • Pass { data: initialInput } to the first promise but wrap it in an runAsyncHelper function which simply runs the promise, awaits the result and returns either { data: result } or { err: Error }
  • and then pass that data to the next promise in the list
  • and so on till the final promise is resolved

The key difference is that we await a lot.

Example

Let's say we have 3 promises to run sequentially:

const p1 = (int) => Promise.resolve(int);
const p2 = (int) => Promise.resolve(int + 1);
const p3 = (int) => Promise.resolve(int * 2);
Enter fullscreen mode Exit fullscreen mode

We can now run them sequentially like so:

const result = await pipePromises(p1, p2, p3)(10); // result = { data: 22 }
Enter fullscreen mode Exit fullscreen mode

What if one of the promises actually failed?

const p1 = (int) => Promise.resolve(int);
const p2 = (int) => Promise.resolve(int + 1);
const p3 = (_) => Promise.reject(new Error('Uh oh'));
const p4 = (int) => Promise.resolve(int * 2);

const result = await pipePromises(p1, p2, p3, p4)(10); // result = { err: new Error("Uh oh") }
Enter fullscreen mode Exit fullscreen mode

A failed promise is like a short-circuit. The first promise to fail will be returned as the result of the pipe.

Sentry blog image

How to reduce TTFB

In the past few years in the web dev world, we’ve seen a significant push towards rendering our websites on the server. Doing so is better for SEO and performs better on low-powered devices, but one thing we had to sacrifice is TTFB.

In this article, we’ll see how we can identify what makes our TTFB high so we can fix it.

Read more

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Engage with a sea of insights in this enlightening article, highly esteemed within the encouraging DEV Community. Programmers of every skill level are invited to participate and enrich our shared knowledge.

A simple "thank you" can uplift someone's spirits. Express your appreciation in the comments section!

On DEV, sharing knowledge smooths our journey and strengthens our community bonds. Found this useful? A brief thank you to the author can mean a lot.

Okay