Filtering an array using a function that returns a promise

Praveenkumar
Sep 3, 2020

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

filter promise

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 ]

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]

As you can see, we 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[]>;
  • 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
};

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>,
]

Line 2:

// As a result of Line 2
const booleans = [false, true, false, true, false, true];

Line 3:

// Return at Line 3
Promise<[2, 4, 6]>

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 ]

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])
  );

Hope you enjoyed! Happy Hacking!

promisejavascripttypescript

WRITTEN BY

Praveenkumar

I am a passionate full stack developer. I develop primarily using JS specifically TS. I also work on JAVA and python. I like exploring latest languages, technologies and frameworks.