How to use async functions with Array.filter in Javascript

How to keep only the elements that satisfy an async condition

3 mins

In the first article, we’ve covered how async/await helps with async commands but it offers little help when it comes to asynchronously processing collections. In this post, we’ll look into the filter function, that has probably the most unintuitive way to support async functions.

The filter function

The filter function keeps only the elements that pass a condition. It gets a function, this time it’s called a predicate, and this function returns true/false (truthy and falsy, to be more precise) values. The resulting collection only contains the elements where the predicate returned true.

const arr = [1, 2, 3, 4, 5];

const syncRes = arr.filter((i) => {
	return i % 2 === 0;
});

console.log(syncRes);
// 2,4

Async filter with map

The async version is a bit more complicated this time and it works in two phases. The first one maps the array through the predicate function asynchronously, producing true/false values. Then the second step is a synchronous filter that uses the results from the first step.

asyncresults[F, T, F, T, F]arr[1, 2, 3, 4, 5]result[2, 4](map) i % 2 === 0(filter) results[index]Async filter

const arr = [1, 2, 3, 4, 5];

const asyncFilter = async (arr, predicate) => {
	const results = await Promise.all(arr.map(predicate));

	return arr.filter((_v, index) => results[index]);
}

const asyncRes = await asyncFilter(arr, async (i) => {
	await sleep(10);
	return i % 2 === 0;
});

console.log(asyncRes);
// 2,4

Or a one-liner implementation:

const asyncFilter = async (arr, predicate) => Promise.all(arr.map(predicate))
	.then((results) => arr.filter((_v, index) => results[index]));

Async filter with mapmapasyncFilterasyncFilterasyncMapasyncMape1e1e2e2e3e3filterfilterStep 1[1, 2, 3]falsetruefalse[F, T, F]Step 2[1, 2, 3][F, T, F][2]

Shameless plug
We write articles like this regularly. Join our mailing list and let's keep in touch.

Concurrency

The above implementation runs all of the predicate functions concurrently. This is usually fine, but, as with all other functions, it might strain some resources too hard. Fortunately, since the above implementation relies on map, the same concurrency controls can be used.

Async filter with reduce

Instead of using an async map with a sync filter, an async reduce could also do the job. Since it’s just one function, the structure is even easier though it does not provide the same level of control.

First, start with an empty array ([]). Then run the next element through the predicate function and if it passes, append it to the array. If not, skip it.

[ ][...[], ...[]]predicate(1) =>false[...[], ...[2]]predicate(2) =>true[...[2], ...[]]predicate(3) =>false[ ]123Promise([2])Async filter with reduce

// concurrently
const asyncFilter = async (arr, predicate) => 
	arr.reduce(async (memo, e) =>
		await predicate(e) ? [...await memo, e] : memo
	, []);

Async filter with reduceasyncFilterasyncFilterreducei % 2 === 0reducei % 2 === 0e1e1e2e2e3e3[1, 2, 3]await predicate(e)await memoawait memo[][2][2][2]

Notice that the await predicate(e) comes before the await memo, which means those will be called in parallel.

Sequential processing

To wait for a predicate function to finish before calling the next one, change the order of the awaits:

// sequentially
const asyncFilter = async (arr, predicate) => 
	arr.reduce(async (memo, e) => 
		[...await memo, ...await predicate(e) ? [e] : []]
	, []);

This implementation waits for the previous element then conditionally append an element depending on the result of the predicate (...[e] or ...[]).

Async filter with reduce running sequentiallyasyncFilterasyncFilterreducei % 2 === 0reducei % 2 === 0e1e1e2e2e3e3[1, 2, 3]await memoawait memoawait predicate(1)[]await predicate(2)[2]await predicate(3)[2][2]

Shameless plug
Learn how to implement a secure, robust, and scalable file downloading and uploading solution using S3 signed URLs
Get the Ebook

Not sure if it's for you? Sign up for the free 8-part AWS signed URLs email course and find out.

Conclusion

While an async filter is possible, it works in a way that looks strange at first. And while concurrency controls are still available, they need a bit more planning than for other async functions.

10 March 2020