Filtering an array with a function that returns a promise
Here is a 2017 elegant solution using async/await :
Very straightforward usage:
const results = await filter(myArray, async num => { await doAsyncStuff() return num > 2})
The helper function (copy this into 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:
// 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 functionfunction doAsyncStuff() { return Promise.resolve()}// The helper functionasync 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'll even throw in a CodePen.
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 which wraps the existing filter function:
Note: I've commented out the subclassing, because it's not supported by Babel just yet 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 insteadlet arr = new AsyncArray([1,2,3,4,5]);// Pass in your own predicatearr.filterAsync(async (element) => { return new Promise(res => { setTimeout(() => { res(element > 3); }, 1); });}).then(result => { console.log(result)});
Here's a 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 takes an array and a function that must either return true
or false
or return a promise that resolves to true
or false
, what you asked for (almost, I didn't overload promise rejection because I think that's a bad idea). Let me know if you have any questions about it.