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)