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.

Heroku

This site is built on Heroku

Join the ranks of developers at Salesforce, Airbase, DEV, and more who deploy their mission critical applications on Heroku. Sign up today and launch your first app!

Get Started

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs