Array filtering with a function that returns a promise

Considering

let arr = [1,2,3]; function filter(num) { return new Promise((res, rej) => { setTimeout(() => { if( num === 3 ) { res(num); } else { rej(); } }, 1); }); } function filterNums() { return Promise.all(arr.filter(filter)); } filterNums().then(results => { let l = results.length; // length should be 1, but is 3 }); 

The length is 3 because Promises is returned, not the value. Is there a way to filter an array with a function that returns Promise?

Note. In this example, fs.stat has been replaced by setTimeout, see https://github.com/silenceisgolden/learn-esnext/blob/array-filter-async-function/tutorials/array-filter-with-async-function.js for a specific code.

+36
javascript arrays ecmascript-6 es6-promise
source share
12 answers

As mentioned in the comments, Array.prototype.filter is synchronous and therefore does not support Promises.

Since you can now (theoretically) subclass built-in types with ES6, you should be able to add your own asynchronous method that wraps the existing filter function:

Note. I commented on the subclassification because it is not yet supported by Babel for arrays

 class AsyncArray /*extends Array*/ { constructor(arr) { this.data = arr; // In place of Array subclassing } filterAsync(predicate) { // Take a copy of the array, it might mutate by the time we've finished const data = Array.from(this.data); // Transform all the elements into an array of promises using the predicate // as the promise return Promise.all(data.map((element, index) => predicate(element, index, data))) // Use the result of the promises to call the underlying sync filter function .then(result => { return data.filter((element, index) => { return result[index]; }); }); } } // Create an instance of your subclass instead let arr = new AsyncArray([1,2,3,4,5]); // Pass in your own predicate arr.filterAsync(async (element) => { return new Promise(res => { setTimeout(() => { res(element > 3); }, 1); }); }).then(result => { console.log(result) }); 

Demo Babel REPL

+26
source share

Here is the way:

 var wait = ms => new Promise(resolve => setTimeout(resolve, ms)); var filter = num => wait(1).then(() => num == 3); var filterAsync = (array, filter) => Promise.all(array.map(entry => filter(entry))) .then(bits => array.filter(entry => bits.shift())); filterAsync([1,2,3], filter) .then(results => console.log(results.length)) .catch(e => console.error(e)); 

The filterAsync function accepts an array and a function that should either return true , false , or return a promise that resolves to true or false , what you asked for (almost, I didn’t) t overload promise failure because I think the bad idea). Let me know if you have any questions about this.

 var wait = ms => new Promise(resolve => setTimeout(resolve, ms)); var filter = num => wait(1).then(() => num == 3); var filterAsync = (array, filter) => Promise.all(array.map(entry => filter(entry))) .then(bits => array.filter(entry => bits.shift())); filterAsync([1,2,3], filter) .then(results => console.log(results.length)) .catch(e => console.error(e)); var console = { log: msg => div.innerHTML += msg + "<br>", error: e => console.log(e +", "+ (e.lineNumber-25)) }; 
 <div id="div"></div> 
+25
source share

Here is an elegant 2017 solution using async / await:

Very simple use:

 const results = await filter(myArray, async num => { await doAsyncStuff() return num > 2 }) 

Helper function (copy this to your web page):

 async function filter(arr, callback) { const fail = Symbol() return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail) } 

Demo version:

 // Async IIFE (async function() { const myArray = [1, 2, 3, 4, 5] // This is exactly what you'd expect to write const results = await filter(myArray, async num => { await doAsyncStuff() return num > 2 }) console.log(results) })() // Arbitrary asynchronous function function doAsyncStuff() { return Promise.resolve() } // The helper function async function filter(arr, callback) { const fail = Symbol() return (await Promise.all(arr.map(async item => (await callback(item)) ? item : fail))).filter(i=>i!==fail) } 

I will even add CodePen .

+23
source share

Promise the gearbox to help!

 [1, 2, 3, 4].reduce((op, n) => { return op.then(filteredNs => { return new Promise(resolve => { setTimeout(() => { if (n >= 3) { console.log("Keeping", n); resolve(filteredNs.concat(n)) } else { console.log("Dropping", n); resolve(filteredNs); } }, 1000); }); }); }, Promise.resolve([])) .then(filteredNs => console.log(filteredNs)); 

The gears are awesome. “Reduce my problem to my goal” seems to be a pretty good strategy for something more complex than simple solutions for you, that is, filtering an array of things that are not all available right away.

+10
source share

For typewritten folk (or es6 just remove the type syntax)

 function mapAsync<T, U>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<U>): Promise<U[]> { return Promise.all(array.map(callbackfn)); } async function filterAsync<T>(array: T[], callbackfn: (value: T, index: number, array: T[]) => Promise<boolean>): Promise<T[]> { const filterMap = await mapAsync(array, callbackfn); return array.filter((value, index) => filterMap[index]); } 

ES6

 function mapAsync(array, callbackfn) { return Promise.all(array.map(callbackfn)); } async function filterAsync(array, callbackfn) { const filterMap = await mapAsync(array, callbackfn); return array.filter((value, index) => filterMap[index]); } 
+4
source share

Late to the game, but since no one else mentioned it, Bluebird supports Promise.map, which is my move for filters that require aysnc processing for the condition,

 function filterAsync(arr) { return Promise.map(arr, num => { if (num === 3) return num; }) .filter(num => num !== undefined) } 
+3
source share

AsyncFilter Method:

 Array.prototype.asyncFilter = async function(f){ var array = this; var booleans = await Promise.all(array.map(f)); return array.filter((x,i)=>booleans[i]) } 
+1
source share

The right way to do this (but seems too confusing):

 let arr = [1,2,3]; function filter(num) { return new Promise((res, rej) => { setTimeout(() => { if( num === 3 ) { res(num); } else { rej(); } }, 1); }); } async function check(num) { try { await filter(num); return true; } catch(err) { return false; } } (async function() { for( let num of arr ) { let res = await check(num); if(!res) { let index = arr.indexOf(num); arr.splice(index, 1); } } })(); 

Again, it seems too confusing.

0
source share

I think it could be easier and even one liner

You can easily wrap the filter () inside Promise. solve () one

 Promise.resolve(arr.filter(item => item.length > 2)) 
0
source share

@ DanRoss's option:

 async function filterNums(arr) { return await arr.reduce(async (res, val) => { res = await res if (await filter(val)) { res.push(val) } return res }, Promise.resolve([])) } 

Please note that if (as in the current case) you do not need to worry about filter () having side effects that need to be serialized, you can also:

 async function filterNums(arr) { return await arr.reduce(async (res, val) => { if (await filter(val)) { (await res).push(val) } return res }, Promise.resolve([])) } 
0
source share

If someone is interested in a modern typewriting solution (the error symbol is used to filter):

 const failSymbol = Symbol(); export async function filterAsync<T>( itemsToFilter: T[], filterFunction: (item: T) => Promise<boolean>, ): Promise<T[]> { const itemsOrFailFlags = await Promise.all( itemsToFilter.map(async (item) => { const hasPassed = await filterFunction(item); return hasPassed ? item : failSymbol; }), ); return itemsOrFailFlags.filter( (itemOrFailFlag) => itemOrFailFlag !== failSymbol, ) as T[]; } 
0
source share

You can do something like this ...

 theArrayYouWantToFilter = await new Promise(async (resolve) => { const tempArray = []; theArrayYouWantToFilter.filter(async (element, index) => { const someAsyncValue = await someAsyncFunction(); if (someAsyncValue) { tempArray.push(someAsyncValue); } if (index === theArrayYouWantToFilter.length - 1) { resolve(tempArray); } }); }); 

Wrapped in an asynchronous function ...

 async function filter(theArrayYouWantToFilter) { theArrayYouWantToFilter = await new Promise(async (resolve) => { const tempArray = []; theArrayYouWantToFilter.filter(async (element, index) => { const someAsyncValue = await someAsyncFunction(); if (someAsyncValue) { tempArray.push(someAsyncValue); } if (index === theArrayYouWantToFilter.length - 1) { resolve(tempArray); } }); }); return theArrayYouWantToFilter; } 
0
source share

All Articles