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(...)
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
}
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 }
);
};
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);
We can now run them sequentially like so:
const result = await pipePromises(p1, p2, p3)(10); // result = { data: 22 }
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") }
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)