I was working on a project when I ran into a use case where I needed to use Array’s reduce
method with some async/await
(Promise
based) network calls. That’s when things actually became confusing and complicated. Let’s learn about how to use async/await with array reduce by running through an example.
Problem
Get all the pull request objects using GitHub API from a repo who have a commit message not starting with Fix:
or Docs:
.
We will use a lot of helper methods to talk to GitHub API and do some other operation but will not discuss those in this post as we want to focus async/await
with array reduce.
Solution
Let create a function named getNonSemverPatchPRs
which is going to return all the PR objects from a repo who doesn’t not qualify as semver patch PR based on a the commit message of the PR. Expectation here will be that when we call the getNonSemverPatchPRs
it should return a Promise
and that promise should resolve with a array of PR objects.
const getNonSemverPatchPRs = async () => {
const allOpenPrs = await getAllOpenPRs();
return allOpenPrs.reduce((collection, pr) => {
// 1. This is where we want to get all the commits of the PR in context
// 2. Then we want to see if the commit message of the first commit message starts with `Fix:` or `Docs:`
// 3. If yes then ignore it otherwise add it to the collection.
}, []);
};
Fetch all the commits for a PR
To complete step 1 we need to perform a network call to fetch all the commit for a PR. Now this call will be promised based. Since we have to make await
the call we need to make the reduce handler function async
.
const getNonSemverPatchPRs = async () => {
const allOpenPrs = await getAllOpenPRs();
return allOpenPrs.reduce(async (collection, pr) => {
const allCommits = await getAllCommitsForaPR(pr.number);
// 2. Then we want to see if the commit message of the first commit message starts with `Fix:` or `Docs:`
// 3. If yes then ignore it otherwise add it to the collection.
}, []);
};
Check the commit message from the first commit
Now we are going to check the commit message of the first commit to see if it starts with Fix:
or Docs:
. This call is synchronous call to a helper function.
const getNonSemverPatchPRs = async () => {
const allOpenPrs = await getAllOpenPRs();
return allOpenPrs.reduce(async (collection, pr) => {
const allCommits = await getAllCommitsForaPR(pr.number);
const isNotSemverPatchPR = checkCommitMessageForPatch(allCommits[0]);
// 3. If yes then ignore it otherwise add it to the collection.
}, []);
};
Add to collection if PR is not semver patch PR
Now we are going to check if it’s not a semver patch PR then add to collection of reduce otherwise ignore it.
const getNonSemverPatchPRs = async () => {
const allOpenPrs = await getAllOpenPRs();
return allOpenPrs.reduce(async (collection, pr) => {
const allCommits = await getAllCommitsForaPR(pr.number);
const isNotSemverPatchPR = checkCommitMessageForPatch(allCommits[0]);
if (isNotSemverPatchPR) {
collection.push(pr);
}
return collection;
}, []);
};
Problem inside reduce with async function handler
Thought: Based on your knowledge of
async/await
and arrayreduce
, you would think that it will keep pushing thepr
objects to thecollection
and return thecollection
so that next iteration of the reduce can use it and keep adding stuff to the collection.Reality: The reduce callback function is an async function so it will always returns a
Promise
. Since it returns aPromise
the value of thecollection
parameter is not an array but its aPromise
from the previous execution.-
Solution: Since
collection
always contains aPromise
then we need to resolve that promise to get the response which will finally become our collection and then we can keep pushing stuff to it and then return that as part of the function.- Make the initial value of reduce to be a dummy resolved
Promise
and then we can keep resolving the promises returned by every call. - Make a collection inside the function which can be extracted by resolving the passed in Promise.
- Make the initial value of reduce to be a dummy resolved
const getNonSemverPatchPRs = async () => {
const allOpenPrs = await getAllOpenPRs();
return allOpenPrs.reduce(async (previousPromise, pr) => {
const collection = await previousPromise;
const allCommits = await getAllCommitsForaPR(pr.number);
const isNotSemverPatchPR = checkCommitMessageForPatch(allCommits[0]);
if (isNotSemverPatchPR) {
collection.push(pr);
}
return collection;
}, Promise.resolve([]));
};
Conclusion
- Would recommend running through the above example and try to put breakpoints to better understand the flow. Feel free to use the JSBin to play around.
-
async
function always return aPromise
that is why the reduce function starts accumulating previousPromise
. When this promise resolves it gives you the real collection retuned inside the function.
I am still working through this but I wanted to write something about this so that I can help others who run into this. Feel free to leave feedback in the comments below.
Top comments (0)