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.

Top comments (0)