TechnoCorner
TechnoCorner

Reputation: 5135

Promise.all always returns an empty array

I'm trying to solve this promise puzzle and I've had 2 questions:

A) I'm wondering why it's returning empty array. What am i doing wrong?

B) How can I implement async reduce?

B) How can I make it return a Async Array instead of empty array?

Note:

Please make use of .get method to iterate through the elements and the return value must be a asyncArray (not a regular array)

Code looks like this:

  /**
     * Async array.
     */
    function AsyncArray(arr) {
      this._arr = arr;
      this.length = arr.length;
    }
    
    /**
     * Asynchronously get the array item of the
     * given index.
     * @param {number} index - array index of the desired item
     * @param {function} callback - called with the array item
     */
    AsyncArray.prototype.get = function get(index, callback) {
      setTimeout(callback, 0, this._arr[index]);
    };
    
    
    /**
     * Async version of Array.prototype.map.
     * @param {AsyncArray} arr
     * @param {function} fn - (item: any) => any
     * @returns {Promise<AsyncArray>}
     */
    function asyncMap(arr, fn) {
      
      let counter = 0; // counter
      const res = []; /// array of promises.
      const len = arr.length;
      
      // Get the length.
      return new Promise((resolve, reject) => { // Pending.
        
        while(true) {
          if(counter===len) {
            console.log("before break", res);
            break;
          }
          
          arr.get(counter, item => {
            res[counter] = function() {
              return new Promise((resolve, reject) => {
                return resolve(fn(item));
              });
            }();
            
            console.log('r',res);
          });
          counter += 1;
        }
      
    
        Promise.all(res).then((r1, rej) => {
          console.log("hello world", r1);
          return resolve(res); 
        });
      });
    }
    
    /**
     * Async version of Array.prototype.reduce.
     * @param {AsyncArray} arr
     * @param {function} fn - (val: any, item: any) => any
     * @returns {Promise<any>}
     */
    function asyncReduce(arr, fn, initVal) {}
    
    
    const arr = new AsyncArray([1, 2, 3]);
    
    // arr.get(1, item => console.log(item)); // Existing
    
    // Expected result: [2, 4, 6];
    asyncMap(arr, x => x * 2).then(arr_ => console.log('asyncMap:', arr_));
    
    // Expected result: 106
    // asyncReduce(arr, (v, x) => v + x, 100).then(val => console.log('asyncReduce:', val));
    

Upvotes: 2

Views: 3042

Answers (1)

CertainPerformance
CertainPerformance

Reputation: 370729

You're putting the result of the get calls into the results array asynchronously - res is empty when you call Promise.all on it:

      arr.get(counter, item => {
        res[counter] = function() {
          return new Promise((resolve, reject) => {
            return resolve(fn(item));
          });
        }();
      });
      // at this point, nothing has been pushed to res

Promise.all(res)

called synchronously after that point won't wait for anything, because none of the items in the array are Promises.

You could push a Promise to the array synchronously instead:

      res.push(new Promise((resolve) => {
        arr.get(counter, item => resolve(fn(item)));
      }));

/**
     * Async array.
     */
    function AsyncArray(arr) {
      this._arr = arr;
      this.length = arr.length;
    }
    
    /**
     * Asynchronously get the array item of the
     * given index.
     * @param {number} index - array index of the desired item
     * @param {function} callback - called with the array item
     */
    AsyncArray.prototype.get = function get(index, callback) {
      setTimeout(callback, 0, this._arr[index]);
    };
    
    
    /**
     * Async version of Array.prototype.map.
     * @param {AsyncArray} arr
     * @param {function} fn - (item: any) => any
     * @returns {Promise<AsyncArray>}
     */
    function asyncMap(arr, fn) {
      
      let counter = 0; // counter
      const res = []; /// array of promises.
      const len = arr.length;
      
      // Get the length.
      return new Promise((resolve, reject) => { // Pending.
        
        while(true) {
          if(counter===len) {
            console.log("before break", res);
            break;
          }
          res.push(new Promise((resolve) => {
            arr.get(counter, item => resolve(fn(item)));
          }));
          counter += 1;
        }
      
    
        Promise.all(res).then((r1, rej) => {
          console.log("hello world", r1);
          return resolve(r1); 
        });
      });
    }
    
    /**
     * Async version of Array.prototype.reduce.
     * @param {AsyncArray} arr
     * @param {function} fn - (val: any, item: any) => any
     * @returns {Promise<any>}
     */
    function asyncReduce(arr, fn, initVal) {}
    
    
    const arr = new AsyncArray([1, 2, 3]);
    
    // arr.get(1, item => console.log(item)); // Existing
    
    // Expected result: [2, 4, 6];
    asyncMap(arr, x => x * 2).then(arr_ => console.log('asyncMap:', arr_));
    
    // Expected result: 106
    // asyncReduce(arr, (v, x) => v + x, 100).then(val => console.log('asyncReduce:', val));

I'd prefer to use .map on an array of the internal array's length to map each arr.get call to a Promise in an array, and call Promise.all on that array:

function AsyncArray(arr) {
  this._arr = arr;
  this.length = arr.length;
}
AsyncArray.prototype.get = function get(index, callback) {
  setTimeout(callback, 0, this._arr[index]);
};
function asyncMap(asyncArr, fn) {
  return Promise.all(
    new Array(asyncArr.length).fill().map(
      (item, i) => new Promise(resolve => asyncArr.get(i, item => resolve(fn(item))))
    )
  ).then((result) => {
      console.log("hello world", result);
      return result;
    });
}
const arr = new AsyncArray([1, 2, 3]);
asyncMap(arr, x => x * 2).then(arr_ => console.log('asyncMap:', arr_));

For async reduce, have the accumulator be a Promise that resolves to the array after having been pushed in the last iteration:

function asyncReduce(asyncArr, fn, initVal) {
  return new Array(asyncArr.length).fill().reduce(
    (a, _, i) => a.then(resultsArr => {
      // feel free to use asynchronous operations here
      return new Promise((resolve) => {
        asyncArr.get(i, resultItem => {
          resultsArr.push(fn(resultItem));
          resolve(resultsArr);
        });
      });
    }),
    Promise.resolve(initVal)
  );
}

function AsyncArray(arr) {
  this._arr = arr;
  this.length = arr.length;
}
AsyncArray.prototype.get = function get(index, callback) {
  setTimeout(callback, 0, this._arr[index]);
};
const arr = new AsyncArray([1, 2, 3]);
asyncReduce(arr, x => x * 2, []).then(arr_ => console.log('asyncReduce:', arr_));

To also return instances of asyncArray, just call new AsyncArray before resolving, for example:

function asyncReduce(asyncArr, fn, initVal) {
  return new Array(asyncArr.length).fill().reduce(
    (a, _, i) => a.then(resultsArr => {
      // feel free to use asynchronous operations here
      return new Promise((resolve) => {
        asyncArr.get(i, resultItem => {
          resultsArr.push(fn(resultItem));
          resolve(resultsArr);
        });
      });
    }),
    Promise.resolve(initVal)
  )
    .then((resultArr) => new AsyncArray(resultArr));
}

function AsyncArray(arr) {
  this._arr = arr;
  this.length = arr.length;
}
AsyncArray.prototype.get = function get(index, callback) {
  setTimeout(callback, 0, this._arr[index]);
};
const arr = new AsyncArray([1, 2, 3]);
asyncReduce(arr, x => x * 2, []).then(arr_ => console.log('asyncReduce:', arr_));

Upvotes: 4

Related Questions