Filtering an array using a function that returns a promise

praveenkumarrr

Praveen

Posted on September 3, 2020

Filtering an array using a function that returns a promise

Things get interesting when you want to filter an array of values, with a function returning promise of boolean instead of just a boolean

Prerequisite:

  • Basic understanding on how the promises work.
  • Basic knowledge of Typescript.

It is easy to filter an array of values using a function returning a boolean. Let see an example.

const values = [1, 2, 3, 4, 5, 6];
const isEven = (v: number) => v % 2 === 0;
const result = values.filter(isEven);
console.log(result);

// Output
// [ 2, 4, 6 ]
Enter fullscreen mode Exit fullscreen mode

In the above code, we use a function called isEven to filter an array of numbers and return only the even numbers. We know that, the isEven function takes a number and returns a boolean value representing, whether the number is even or not.

Lets change the isEven function to return Promise<boolean> instead of just a boolean and try to filter values.

const values = [1, 2, 3, 4, 5, 6];
const isEvenPromise = (v: number) => new Promise(res => res(v % 2 === 0));
const result = values.filter(isEvenPromise);

// Output
// [1, 2, 3, 4, 5, 6]
Enter fullscreen mode Exit fullscreen mode

As you can see, i got all the values in the output, which is wrong. Now, why did that happen?

This happened because the filter got a Promise as a result of executing the isEvenPromise function and not a boolean. As per the javascript's truthy concept, an object is always true, hence all the values are returned as output.

Now we know what the problem is, but how to solve this? Lets write a function to solve this.

First, lets define the type of our function to get a clear idea of how the function is going to look like.

type Filter = <T>(values: T[], fn: (t: T) => Promise<boolean>) => Promise<T[]>;
Enter fullscreen mode Exit fullscreen mode
  • First parameter is the array of values of type T that has to be filtered.
  • Second parameter is a function that accepts a value of type T as an input and returns a Promise, of type boolean.
  • Return type is a Promise, holding an array of type T.

One thing to be noted is that, the return type of this function is not T[] but Promise<T[]>. This is because, the filter function does not return a boolean but returns a Promise<boolean>. We cannot remove the value out of a Promise. The only we to use the value returned from a Promise is either by using a then or by using async and await.

Now lets write the body of the function.

const filterPromise: Filter = async (values, fn) => {
    const promises = values.map(fn);                // Line 1
    const booleans = await Promise.all(promises);   // Line 2
    return values.filter((_, i) => booleans[i]);    // Line 3
};
Enter fullscreen mode Exit fullscreen mode

One important thing to note here is that,

filter function in JS passes the index of the array as a second argument to the accepted function.

In Line 1, we map the values array to the fn instead of filtering it directly, so that we can obtain the boolean values first. In Line 2, we convert the array of promises into a promise, holding an array of booleans. We use the await keyword here to access the booleans. In Line 3, we filter the values using the ith element in the booleans array which holds the boolean value of ith element.

A representation of what each variable will hold as a result of the execution of each line is shown below.

For input values [1, 2, 3, 4, 5, 6],

Line 1:

// As a result of Line 1
const promises = [
    Promise<false>,
    Promise<true>,
    Promise<false>,
    Promise<true>,
    Promise<false>,
    Promise<true>,
]
Enter fullscreen mode Exit fullscreen mode

Line 2:

// As a result of Line 2
const booleans = [
    false,
    true,
    false,
    true,
    false,
    true
]
Enter fullscreen mode Exit fullscreen mode

Line 3:

// Return at Line 3
Promise<[2, 4, 6]>
Enter fullscreen mode Exit fullscreen mode

As you can see, the result at Line 3 is properly filtered even numbers from the input array.

The entire code is shown below.

const values = [1, 2, 3, 4, 5, 6];
const isEvenPromise = (v: number): Promise<boolean> => new Promise(res => res(v % 2 === 0));

type Filter = <T>(values: T[], fn: (t: T) => Promise<boolean>) => Promise<T[]>;
const filterPromise: Filter = async (values, fn) => {
    const promises = values.map(fn);                // Line 1
    const booleans = await Promise.all(promises);   // Line 2
    return values.filter((_, i) => booleans[i]);    // Line 3
};

const result = filterPromise<number>(values, isEvenPromise);

result.then(d => console.log(d));

// Output
// [ 2, 4, 6 ]
Enter fullscreen mode Exit fullscreen mode

If you are a fan of one liner like me, then the filterPromise function can be written in a single line like below.

const filterPromise = (values, fn) => 
    Promise.all(values.map(fn)).then(booleans => values.filter((_, i) => booleans[i]));
Enter fullscreen mode Exit fullscreen mode

Hope you enjoyed! Happy Hacking!

💖 💪 🙅 🚩
praveenkumarrr
Praveen

Posted on September 3, 2020

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related