Implement async filter function in JavaScript

Implement a function that takes an array of input and an async iteratee function and returns a promise that resolves with the list of inputs that has passed the test through iteratee function in JavaScript.

The inputs will run in parallel, but the output will be in the same order as the original.

The asynchronous iteratee function will accept an input and a callback. The callback function will be called when the input is finished processing, the first argument of the callback will be the error flag and the second will be the result.

Example

Input:
let numPromise = filter([1, 2, 3, 4, 5], function (num, callback) {
  setTimeout(function () {
    num = num * 2;
    console.log(num);
    
    // throw error
    if(num === 7){
      callback(true);
    }else{
      callback(null, num !== 4);
    }
    
  }, 2000);
});
//
numPromise
  .then((result) => console.log("success:" + result))
  .catch(() => console.log("no success"));

Output:
2
4
6
8
10
"success:1,3,4,5"

This function is a simple extension of Array.filter() to handle the async operations.

To implement this we will create a new promise and return it, inside this promise, run all the input values in parallel using the Array.forEach() and inside the forEach pass each value to the iteratee function.

Inside the iteratee function’s callback if the result is true which means the input has passed the test, then add that input to the result at the current index.

In the end, if we are the last element of the input array, resolve the promise with the result.

Filter the final output to remove the gaps as we have to maintain the order for the passed values. There will no value at the indexes of unpassed values in the output array.

const filter = (arr, fn) => {
  // return a new promise
  return new Promise((resolve, reject) => {
    const output = [];
    let track = 0;
    
    arr.forEach((e, i) => {
      fn(e, (error, result) => {
        // track the no of inputs processed
        track++;
        
        // if the input passes the test
        // add it to the current index
        if(result){
          output[i] = e;
        }
        
        // if the last element of the input array
        if(track >= arr.length){
          // filter the final output with truthy values
          // to return the value in order
          resolve(output.filter(Boolean));
        }
      });
    });
   
  });
};
Input:
let numPromise = filter([1, 2, 3, 4, 5], function (num, callback) {
  setTimeout(function () {
    num = num * 2;
    console.log(num);
    
    // throw error
    if(num === 7){
      callback(true);
    }else{
      callback(null, num !== 4);
    }
    
  }, 2000);
});

numPromise
  .then((result) => console.log("success:" + result))
  .catch(() => console.log("no success"));

Output:
2
4
6
8
10
"success:1,3,4,5"