0

I wish to call an asynchronous function in a for loop. I am having significant trouble doing so and am getting a variety of errors such as undefined variables and such.

Evaluator.prototype.asyncEval = function(predictor) {
let self = this;
let metric = 0; //METRICS SHOULD BE UPDATED BY ASYNC FUNCTION

 for (let i = 1; i < this.fullTraces.length; i++) {
      (function(index){
          let deltaTime = self.fullTraces[i][2] - this.fullTraces[i-1][2];
          let subTraces = self.fullTraces.slice(0, i);

          predictor.predict(subTraces, (dist) => { // ASYNC FUNCTION
              if (dist !== null) {
                  let result = dist.getTopK(1);
                  let pX = result[0][0][0];
                  let pY = result[0][0][1];
                  let x = self.fullTraces[i][0];
                  let y = self.fullTraces[i][1];
                  let a = pX - x;
                  let b = pY - y;

                  metric += Math.sqrt(a*a + b*b);
              }
          });
      }(i));
  }
  metric /= this.fullTraces.length - 1;
  return metric;

}

My asynchronous function predictor.predict() is actually using a POST request to get results from my web server.

YourPredictor.prototype.predict = function(trace, callback) {
      return asyncPostRequest('https://0.0.0.0:5000/prediction', trace, responseText => {
              prediction = JSON.parse(responseText);
              let pred = [prediction['xs'], prediction['ys'], 'm'];
              let dist = Dist.NaiveDistribution.from(pred, mouseToKey);
              dist.set(pred, 1);
              callback(dist);
      });
  }

How can I get this to work? I am running this on Chrome. I know there is the new await and async from ES7, but I don't want to use something that bleeding edge yet.

mrQWERTY
  • 4,039
  • 13
  • 43
  • 91
  • If you can not or do not want to use `await`, `async` then you need to refactor you loop and use either a Promise library that supports `each` or even better one that supports `reduce`, or a non Promise base library like [async](http://caolan.github.io/async/). Beside that a function that contains `async` in the the name should not `return` the result as value, but take a callback or return a Promise. – t.niese Mar 06 '17 at 06:27
  • Your problem has nothing to do with the for-loop. You're using `metric` below the for-loop before your predictor callback{s) have had a chance to run. – jib Mar 07 '17 at 00:50
  • Do you have any suggestions how to fix this? I am new to javascript and the concept of async programming is difficult for me. – mrQWERTY Mar 07 '17 at 00:51
  • Do you intend the asynchronous actions to run in parallel or in sequence? Either way, I'd recommend checking out [this question](http://stackoverflow.com/questions/14220321/how-do-i-return-the-response-from-an-asynchronous-call) first, then read up on promises and check out [this one](http://stackoverflow.com/questions/32028552/es6-promises-something-like-async-each). – jib Mar 07 '17 at 01:15
  • I intend for them to run in sequence. I am fine if the whole thread blocks to wait for the async function to return. – mrQWERTY Mar 07 '17 at 01:15

3 Answers3

0

You need to refactor the code to replace the loop with a self-invoking loop, so that each time the asynchronousis called, the result from it is handed back, then iteration is checked, if i

Since the main code is asynchronous you will also need a callback for the initial call function (the doneCallback below).

Example

I left in the original code where it is expected to work, but made a couple of changes for it to work here.

function Evaluator() {}; // dummy for test
Evaluator.prototype.asyncEval = function(predictor, doneCallback) {
  let self = this;
  let metric = 0;
  let length = 10; //this.fullTraces.length;
  let i = 1;

  // replaces for-loop
  (function loop() {
    self.predict(0, (dist) => {
      // ...
      metric += dist;
      if (++i < length) loop();
      else {
        // ...
        doneCallback(metric);
      }
    });
  })();
}

// note: I changed prototype parent in this example
Evaluator.prototype.predict = function(trace, callback) {
  //...
  setTimeout(callback, 100, Math.random() * 100); // simulate async call
}

// TEST
var test = new Evaluator();
test.asyncEval(0, function(result) {
  document.querySelector("div").innerHTML = result;
});
<div>Calcing...</div>

Example leaving the original code in place at the intended locations:

function Evaluator() {}; // dummy for test
Evaluator.prototype.asyncEval = function(predictor, doneCallback) {
  let self = this;
  let metric = 0; //METRICS SHOULD BE UPDATED BY ASYNC FUNCTION
  let length = 10; //this.fullTraces.length;
  let i = 1;

  // replaces for-loop
  (function loop() {
    //let deltaTime = self.fullTraces[i][2] - this.fullTraces[i - 1][2];
    //let subTraces = self.fullTraces.slice(0, i);

    self.predict(0, (dist) => { // ASYNC FUNCTION
      //predictor.predict(subTraces, (dist) => { // ASYNC FUNCTION
      /*if (dist !== null) {
       let result = dist.getTopK(1);
       let pX = result[0][0][0];
       let pY = result[0][0][1];
       let x = self.fullTraces[i][0];
       let y = self.fullTraces[i][1];
       let a = pX - x;
       let b = pY - y;

       metric += Math.sqrt(a * a + b * b);
      }*/
      metric += dist;
      if (++i < length) loop();
      else {
        //metric /= this.fullTraces.length - 1;
        //return metric; <- don't use, instead use:
        doneCallback(metric);
      }
    });
  })();
}

// note: I changed prototype parent in this example
Evaluator.prototype.predict = function(trace, callback) {
  setTimeout(callback, 100, Math.random() * 100); // simulate async call
  /*return asyncPostRequest('https://0.0.0.0:5000/prediction', trace, responseText => {
          prediction = JSON.parse(responseText);
          let pred = [prediction['xs'], prediction['ys'], 'm'];
          let dist = Dist.NaiveDistribution.from(pred, mouseToKey);
          dist.set(pred, 1);
          callback(dist);
  });*/
}

// TEST
var test = new Evaluator();
test.asyncEval(0, function(result) {
  document.querySelector("div").innerHTML = result;
});
<div>Calcing...</div>
  • I think you mean for this: `if (i++ < length)` to be this: `if (++i < length)`, otherwise the condition considers the current value of `i`, and then increments it after, making `i == length` in the final call of `loop()`. –  Mar 09 '17 at 22:36
  • 1
    @squint yes I did, I was slightly "offset" by `i` starting at 1 :), not sure OP wants that, in either case, updated. –  Mar 09 '17 at 22:42
0

If you dont want to use async + await combination, I would suggest to take a look at this post. Asynchronous for cycle in JavaScript

I'm using this asyncLoop function and it's working great:

The function takes three arguments: 1) iterations, 2) a loop callback function and 3) Done callback function, Check out the code:

function promise1(param){
    return new Promise((resolve, reject) => setTimeout(() => { resolve(`Promise1 ${param} Done`)}, 2000))
}


function asyncLoop(iterations, func, callback) {
    var index = 0;
    var done = false;
    var loop = {
        next: function() {
            if (done) {
                return;
            }

            if (index < iterations) {
                index++;
                func(loop);

            } else {
                done = true;
                callback();
            }
        },

        iteration: function() {
            return index - 1;
        },

        break: function() {
            done = true;
            callback();
        }
    };
    loop.next();
    return loop;
}

var asyncProc = ["Process1", "Process2", "Process3"]
asyncLoop
(
    asyncProc.length,
  (loop) => {   promise1(asyncProc[loop.iteration()]).then((msg) =>{ console.log(msg); loop.next() }) },
  () => { console.log("ALL DONE!")});
Community
  • 1
  • 1
funcoding
  • 741
  • 6
  • 11
0

You cannot return the value of the "metric" synchronously if it is being modified asynchronously. You'll need to pass a callback into your method so the "metric" can be returned when it is ready.

Evaluator.prototype.asyncEval = function (predictor, callback) {

    let self = this;
    let metric = 0; //METRICS SHOULD BE UPDATED BY ASYNC FUNCTION
    let callbacks = 0; // Keep a counter for the asynchronous callbacks
    for (let i = 1; i < self.fullTraces.length; i++) {
        let deltaTime = self.fullTraces[i][2] - this.fullTraces[i - 1][2];
        let subTraces = self.fullTraces.slice(0, i);

        // Queue up an asynchronous callback
        predictor.predict(subTraces, (dist) => { // ASYNC FUNCTION
            if (dist !== null) {
                let result = dist.getTopK(1);
                let pX = result[0][0][0];
                let pY = result[0][0][1];
                let x = self.fullTraces[i][0];
                let y = self.fullTraces[i][1];
                let a = pX - x;
                let b = pY - y;

                metric += Math.sqrt(a * a + b * b);
            }

            // Decrement the counter and check if we're done
            if (--callbacks === 0) {
                callback(metric / (self.fullTraces.length - 1));
            }
        });

        // Increment the counter
        callbacks++;
    }
};
Jake Holzinger
  • 5,783
  • 2
  • 19
  • 33