Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Filtering an array with a function that returns a promise

Given

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 are returned, not values. Is there a way to filter the array with a function that returns a Promise?

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

like image 555
ajklein Avatar asked Oct 26 '15 21:10

ajklein


People also ask

Can a normal function return a promise?

As a side note, we can return a promise from any function. It doesn't have to be asynchronous. That being said, promises are normally returned in cases where the function they return from is asynchronous. For example, an API that has methods for saving data to a server would be a great candidate to return a promise!

Is JavaScript filter synchronous?

filter() method is having an asynchronous behavior.

Does promise all return a promise?

The Promise. all() method takes an iterable of promises as an input, and returns a single Promise that resolves to an array of the results of the input promises. This returned promise will fulfill when all of the input's promises have fulfilled, or if the input iterable contains no promises.


2 Answers

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 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'll even throw in a CodePen.

like image 137
Gabe Rogan Avatar answered Oct 22 '22 07:10

Gabe Rogan


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

Babel REPL Demo

like image 33
CodingIntrigue Avatar answered Oct 22 '22 06:10

CodingIntrigue