2

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));
    
TechnoCorner
  • 4,879
  • 10
  • 43
  • 81
  • Avoid mixting direct callbacks with .then() style callbacks. If you are forced to work with an API that employs direct callbacks, then it should be [promisified](https://stackoverflow.com/questions/22519784/how-do-i-convert-an-existing-callback-api-to-promises) at the lowest level. – Roamer-1888 Mar 21 '19 at 14:35
  • Great suggestion. Can you add more details? – TechnoCorner Mar 21 '19 at 14:36
  • I don't have time to write an answer, but you might consider promisifying `AsyncArray.prototype.get()` either by rewriting it or by writing an `AsyncArray.prototype.getAsync()`. Either way, make sure you have a method that returns Promise. The rest of the code will then simplify significantly. – Roamer-1888 Mar 21 '19 at 14:41
  • Your `asyncMap()` should boil down to one or two lines. – Roamer-1888 Mar 21 '19 at 14:43

1 Answers1

4

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_));
CertainPerformance
  • 356,069
  • 52
  • 309
  • 320