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 ]
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, 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[]>;
- First parameter is the array of
values
of typeT
that has to be filtered. - Second parameter is a function that accepts a value of type
T
as an input and returns aPromise
, of typeboolean
. - Return type is a
Promise
, holding an array of typeT
.
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 theindex
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 i
th element in the booleans
array which holds the boolean value of i
th 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!
Top comments (1)
Hi @devscover ,
setTimeout
doesnot return a promise. The solution provided in this article is for promises. So you have to convert thesetTimeout
to promise. Once I change the below function, I get the correct result.