DEV Community

jonhourcade
jonhourcade

Posted on • Edited on

Mapping, Filtering, and Reducing Async Functions

Map and filter do a lot of heavy lifting for javascript developers. They provide a syntactically-clean way to transform elements in arrays and remove items from lists based on the result of a boolean evaluation. Reduce can be used in place of any higher order function. It iterates over an array and returns one thing: an array, object, value, et cetera.

Web applications are aptly named. They are applications that are accessed through the browser. They don't use local storage because the user should be able to log in from any device and have access to their data. Since they rely on data stored on a distant server, they inevitably use asynchronous code to get and set that data.

What happens when you try to pass an asynchronous function into a higher order function? Async functions return promises. If you pass an async function into map or filter, you'll get back an array of resolved promises. This is not the behavior you're looking for. Luckily, there is a quick fix.

Enter the all method of the Promise class. All is a static method with one parameter: an array of promises. Only after all promises in the array have resolved will Promise.all resolve. All you have to do is wrap your higher order function in Promise.all. Put an await keyword in front of Promise.all and make sure the outer function is asynchronous, as well.

So when is this useful? This technique comes in handy when you need to map or filter an array and, inside the callback function, make another API call or get some data from a database.

In the following silly example, I'm using the Star Wars API to return a list of character objects. Each character object has a name property and a homeworld property. The name key points to the character name and the homeworld key points to the homeworld name.

To get the characters, I make an API call to the people endpoint. I then map over the characters. For every character, I make an API call to a specific planet endpoint in order to get the name of the planet. Without Promise.all, I get an array of resolved promises. By simply wrapping the map call in Promise.all, I get the desired results.

Alt Text

Reduce is a bit trickier. Here, I've created a function that takes the endpoint for a specific Star Wars movie (I chose Empire, I'm with Dante). The API call returns an object that contains an array of starship endpoints. I iterate over the array of starship endpoints and calculate the average hyperdrive rating. To do this, I have to make an API call to the starship endpoint.

First, you need to add the await keyword in front of the call to reduce. You need to wait for the last promise to resolve before proceeding. This means the outer function must be asynchronous.

Next, pass an async function into reduce. You can name the first parameter of the callback what you'd normally name it or if you want to be a little more overt, you can name it promise, prevProm, et cetera. The second argument passed into reduce must be a resolved promise. To achieve this, pass in an invocation of Promise.resolve. Into Promise.resolve, pass your accumulator. I chose zero cause I'm calculating a total;

Last, the first line or your callback should await the resolution of the accumulator. You can make a new variable or set it equal to itself. Proceed as usual and don't forget to return the accumulator.

Alt Text

Top comments (0)