Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

JS: Get inner function arguments in asynchronous functions and execute callback

Tags:

javascript

I try to write the function that returns all results of asynchronous functions and execute a callback that push into an array and log the result of every async function.

As a waiter that brings all dishes when they are all done. I don't understand how to get the child arguments that should be returned as a result. The code of task and my not working solution is below:

The task:

var dishOne = function(child) {
    setTimeout(function() {
        child('soup');
    }, 1000);
};

var dishTwo = function(child) {
    setTimeout(function() {
        child('dessert');
    }, 1500);
};
waiter([dishOne, dishTwo], function(results) {
    console.log(results); // console output = ['soup', 'dessert']
});

My not working solution:

function child(arg) {
    this.arr.push(arg)
}

function waiter(funcArray, doneAll) {
    var result = {
        arr: []
    };
    let i = 0;
    const x = child.bind(result)
    funcArray.forEach(function(f) {
      f(x)
      i++;
      if(i == 2) {
        doneAll(result.arr)
      }
    });
}
like image 431
mailman_73 Avatar asked Dec 25 '16 11:12

mailman_73


1 Answers

Problem is this part:

funcArray.forEach(function(f) {
  f(x)
  i++;
  if(i == 2) {
    doneAll(result.arr)
  }
});

which is a synchronous function so when you check if(i == 2), you basically check, that you have called all async functions, but they did not returned anything yet, so all you know is, that the functions have been called, but result.arr is not yet populated.

You must move the doneAll(result.arr) expression into child callback, then it will be called by async function as it returns result.

Simpliest solution I can think of is writing your child as

function child(arg) {
    if (this.arr.push(arg) === this.allCount) this.doneAll(this.arr);
}

and in your waiter function enhance result object

var result = {
    arr: []
    , allCount: funcArray.length
    , doneAll: doneAll
};

This shall work, but has one drawback -- position of results does not keep position of functions in funcArray, the position of results is sorted by duration of async function, simply the first resolved would take first result etc. If this is a problem, you must pass also index to your child function to store result at precious position in result array and then the check by arr.length would not work, because JS array returns length as the highest index + 1, so if your last funcArray would fulfill first, it'll fill last index and the length of result.arr will be equal to this.allCount, so for keeping order of result the same as funcArray, you will need to store number of returned results as another number, increase that number with every new result and compare that number to allCount.

Or decrease allCount like so

function child(idx, arg) {
    this.arr[idx] = arg;
    if (--this.allCount === 0) this.doneAll(this.arr);
}

And modify your waiter function

function waiter(funcArray, doneAll) {
    const result = {
        arr: []
        , allCount: funcArray.length
        , doneAll: doneAll
    };

    funcArray.forEach(function(f, i) {
        f(child.bind(result, i));
    });
}
like image 119
Rudolf Gröhling Avatar answered Nov 13 '22 07:11

Rudolf Gröhling