How to use Array.prototype.filter with async?

JavascriptArraysFilterAsync Await

Javascript Problem Overview


Background

I am trying to filter an array of objects. Before I filter, I need to convert them to some format, and this operation is asynchronous.

 const convert = () => new Promise( resolve => {
     setTimeout( resolve, 1000 );
 });

So, my first try was to do something like the following using async/await:

const objs = [ { id: 1, data: "hello" }, { id: 2, data: "world"} ];

objs.filter( async ( obj ) => {
    await convert();
    return obj.data === "hello";
});

Now, as some of you may know, Array.protoype.filter is a function which callback must return either true or false. filter is synchronous. In the previous example, I am returning none of them, I return a Promise ( all async functions are Promises ).

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/filter

So as one can assume, the code before doesn't really work... That assumption is correct.

Problem

To make filter work with an async function, I checked stackoverflow and found this topic:

https://stackoverflow.com/questions/33355528/filtering-an-array-with-a-function-that-returns-a-promise

Unfortunately, the chosen answer is overly complex and uses classes. This won't do for me. I am instead looking for a more simple solution, using simple functions with a functional approach.

There is one solution at the very end, using a map with a callback to simulate a filter:

https://stackoverflow.com/a/46842181/1337392

But I was hoping to fix my filter function, not to replace it.

Questions

  • Is there a way to have an async function inside a filter?
  • If not, what is the simplest replacement I can do?

Javascript Solutions


Solution 1 - Javascript

There is no way to use filter with an async function (at least that I know of). The simplest way that you have to use filter with a collection of promises is to use Promise.all and then apply the function to your collection of results. It would look something like this:

const results = await Promise.all(your_promises)
const filtered_results = results.filter(res => //do your filtering here)

Hope it helps.

Solution 2 - Javascript

Adapted from the article How to use async functions with Array.filter in Javascript by Tamás Sallai, you basically have 2 steps:

  1. One that creates the conditions for an object to pass
  2. One that receives the objects and returns true or false according to conditions

Here's an example

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

function sleep(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms));
    }

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

Solution 3 - Javascript

Use Scramjet fromArray/toArray methods...

const result = await scramjet.fromArray(arr)
                             .filter(async (item) => somePromiseReturningMethod(item))
                             .toArray();

as simple as that - here's a ready example to copy/paste:

const scramjet = require('../../');

async function myAsyncFilterFunc(data) {
    return new Promise(res => {
        process.nextTick(res.bind(null, data % 2));
    });
}

async function x() {
    const x = await scramjet.fromArray([1,2,3,4,5])
        .filter(async (item) => myAsyncFilterFunc(item))
        .toArray();
    return x;
}

x().then(
    (out) => console.log(out),
    (err) => (console.error(err), process.exit(3)) // eslint-disable-line
);

Disclamer: I am the author of scramjet. :)

Solution 4 - Javascript

Build a parallel array to your array which you want to call filter on. Await all of the promises from your filter func, in my eg, isValid. In the callback in filter, use the 2nd arg, index, to index into your parallel array to determine if it should be filtered.

// ===============================================
// common
// ===============================================
const isValid = async (value) => value >= 0.5;
const values = [0.2, 0.3, 0.4, 0.5, 0.6];


// ===============================================
// won't filter anything
// ===============================================
const filtered = values.filter(async v => await isValid(v));
console.log(JSON.stringify(filtered));


// ===============================================
// filters
// ===============================================
(async () => {
  const shouldFilter = await Promise.all(values.map(isValid));
  const filtered2 = values.filter((value, index) => shouldFilter[index]);

  console.log(JSON.stringify(filtered2));
})();

This behavior makes sense since any Promise instance has a truthy value, but it's not intuitive at a glance.

Solution 5 - Javascript

This answer uses library iter-ops, which handles iterable objects, and supports async filtering:

import {pipe, filter, toAsync} from 'iter-ops';

// your input data:
const objs = [{id: 1, data: 'hello'}, {id: 2, data: 'world'}];

const i = pipe(
    toAsync(objs), // make it an async iterable
    filter(async (value) => {
        await convert(); // any async function
        return value.data === 'hello'; // filtering logic
    })
);

for await(const a of i) {
    console.log(a); // filtered data
}

P.S. I'm the author of iter-ops.

Solution 6 - Javascript

    Array.prototype.asyncFilter =function( filterFn) {
       const arr = this;
       return new Promise(function(resolve){
         const booleanArr = [];
         arr.forEach(function (e) { 
            booleanArr.push(filterFn(e))
         })
         Promise.all(booleanArr).then(function (booleanArr) {
           const arr2 = arr.filter(function (e, i) {
             return booleanArr[i]
           })
           resolve(arr2)
         })
       })
    }
/** use it like this**/
const arr=[1,2,3]
arr.asyncFilter(async e=>{}).then(...)

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionFlame_PhoenixView Question on Stackoverflow
Solution 1 - JavascriptmcousillasView Answer on Stackoverflow
Solution 2 - Javascriptce-locoView Answer on Stackoverflow
Solution 3 - JavascriptMichał KarpackiView Answer on Stackoverflow
Solution 4 - JavascriptJames T.View Answer on Stackoverflow
Solution 5 - Javascriptvitaly-tView Answer on Stackoverflow
Solution 6 - Javascriptuser285294View Answer on Stackoverflow