Javascript, calling asynchronous function in a for loop [duplicate] - javascript

This question already has answers here:
How do I return the response from an asynchronous call?
(41 answers)
Closed 5 years ago.
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.

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>

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!")});

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++;
}
};

Related

How do I switch the running codes using the IF?

Bear with me, I just started learning JS yesterday trying to make a HTML clock by putting multiple tutorials' results together.
I made two looping animations using 2 arrays, with the intent of switching between arrays depending on if it's earlier or later than 8pm. I would like for my code to constantly check if it's 8pm every X amount of seconds, and only re-run the script if the IF result or output is different than what it currently is.
const now = new Date();
if (now.getHours() > 20) { // Time checker!!!!!
var hat = ['A','B','C','D'],
a = -1;
(function f(){
a = (a + 1) % hat.length;
const hatformatted = `${(hat[ a ])}`;
document.querySelector(".main").textContent = hatformatted;
setTimeout(f, 1000);
})();
} else {
var hat = ['1','2','3','4'],
a = -1;
(function f(){
a = (a + 1) % hat.length;
const hatformatted = `${(hat[ a ])}`;
document.querySelector(".main").textContent = hatformatted;
setTimeout(f, 1000);
})();
}
I have tried setInterval however it re-runs the script even if the IF result has not changed, and messes up the animation.
I.E - if it's 6pm, it tries to play the animation from the start every 1 second and the frames get messed up.
Any advice or help would be great, thanks!
I tried to save your code.
const checkTimeDelay = 1000;
let isAbove8PM = null;
let curTimeHandler = null;
let intervalId = null;
// checking if is above 8 pm
const checkTime = () => {
const now = new Date();
if (now.getHours() > 20) {
return true;
}
return false;
};
const above8PMHandler = () => {
var hat = ['A','B','C','D'],
a = -1;
return () => {
a = (a + 1) % hat.length;
const hatformatted = `${(hat[ a ])}`;
document.querySelector(".main").textContent = hatformatted;
// setTimeout(f, 1000);
};
};
const before8PMHandler = () => {
var hat = ['1','2','3','4'],
a = -1;
return () => {
a = (a + 1) % hat.length;
const hatformatted = `${(hat[ a ])}`;
document.querySelector(".main").textContent = hatformatted;
// setTimeout(f, 1000);
};
};
// restart clock interval on based on new handler (above8PMHandler|before8PMHandler)
const rebaseClockInterval = () => {
clearInterval(intervalId);
intervalId = setInterval(curTimeHandler, 1000);
};
// main func, checks if we should change clocks handler (above8PMHandler|before8PMHandler)
const clockTick = () => {
const curTimeChecked = checkTime();
if (curTimeChecked === isAbove8PM) { return; }
isAbove8PM = curTimeChecked;
if (isAbove8PM) {
curTimeHandler = above8PMHandler();
} else {
curTimeHandler = before8PMHandler();
}
curTimeHandler();
rebaseClockInterval();
};
// start main func
setInterval(clockTick, checkTimeDelay);
I also provide some ideas here.
function setClockInterval() {
const hat1 = ['A', 'B', 'C', 'D'];
const hat2 = ['1', '2', '3', '4'];
let prevCheck = null;
let count = 0;
const checker = () => {
return new Date().getSeconds() > 20;
};
const next = () => {
count++;
};
const reset = () => {
count = 0;
};
const render = (content) => {
// console.log(`clockInterval ${content}`); // debug output
document.querySelector(".main").textContent = content;
};
const fn = () => {
const check = checker();
const arr = check ? hat1 : hat2;
// empty arr skip
if (arr.length <= 0)
return;
// restart count to 0
if (prevCheck !== null && prevCheck !== check)
reset();
render(arr[count % arr.length]);
prevCheck = check;
next();
};
return setInterval(fn, 1000);
}
// start interval
const clock = setClockInterval();
// stop
clearInterval(clock);
You can greatly simplify your code by removing everything from inside the if block that is repeated and running it afterward. You also need to get a new Date on each iteration, otherwise the array won't change when it gets to the designated time.
I have changed the condition check to be odd/even seconds to show how it swaps between arrays without losing the index.
// Global, could be kept inside closure
let a = -1;
// Function to run each iteration
(function f() {
// OP code
// let hat = new Date().getHours() > 18? ['A','B','C','D'] : ['1','2','3','4'];
// faster iteration as example
let hat = new Date().getSeconds() % 2? ['A','B','C','D'] :['1','2','3','4'];
a = (a + 1) % hat.length;
document.querySelector(".main").textContent = hat[a];
setTimeout(f, 1000);
})();
<div class="main"></div>

One function inside parent function wont run asynchronously

Apologies for being unable to provide working code but, perhaps foolishly, I have a world of functions calling functions and also two calls to an external astar path finder script. I have been moving instances of await, .then and resolve about for days and days now and I just can't get movePlayer(journeyPart2) to wait until await movePlayer(journeyPart1) has completed. I've watched countless videos about promises as well but I can't see where I'm going wrong. I also tried only returning something once the for loop reaches its last instance but this didn't work. I'm really hoping that it is obvious to someone where I'm going wrong. Unfortunately the second movePlayer function takes a variable amount of time to complete, so I can't use a set interval alone to queue the second movePlayer function.
I don't actually need any data to be returned from the movePlayer functions but I understand at least the promise must be returned. How can I make the second movePlayer function wait?
Edit: Note that the else if works fine as movePlayer is only called once.
function movePlayer(journey) {
return new Promise((resolve, reject) => {
for (let i = 0; i < journey.length; i++){
(function(i){
window.setTimeout(function(){
let nextX = journey[i].y;//?SWITCHED X AND Y AS ASTAR SWITCHES IN GRAPH
let nextY = journey[i].x;//?SWITCHED X AND Y AS ASTAR SWITCHES IN GRAPH
let fromTop = (26 + (nextX * 16) + (nextY * 16)) + "px";
let fromLeft = (576 + (nextX * 32) - (nextY * 32)) + "px";
document.getElementById("playerLayer").style.left = fromLeft;
document.getElementById("playerLayer").style.top = fromTop;
}, i * (900));
}(i));
}
resolve("complete")
reject("failure")
})
}
function globalPathFindANDMove(cellNumEnd, levelNumEnd) {
let cellNumStart = playerInfo.cellNow;
let levelNumStart = playerInfo.levelNow;
let j1Complete = false;
if (levelNumStart != levelNumEnd) {
(async function(){
try {
let journeyPart1 = await localPathFind(cellNumStart, undefined, levelNumStart)
let journeyPart2 = await localPathFind(undefined, cellNumEnd, levelNumEnd)
let useless = await movePlayer(journeyPart1)
console.log(useless)
movePlayer(journeyPart2)//this function won't wait until the function above completes
//------------------------------------------------------changePlayerLevel (levelNumEnd);-----------------------CREATE LATER
} catch (err) {
}
})();
} else if (levelNumStart == levelNumEnd) {
let journey = localPathFind(cellNumStart, cellNumEnd, levelNumEnd);
movePlayer(journey);
}
}
It's hard to tell for sure what you are trying to do in your code, but I think you just want to wait until the setTimeout part has run before resolving:
function movePlayer(journey) {
return new Promise((resolve, reject) => {
for (let i = 0; i < journey.length; i++){
(function(i){
window.setTimeout(function(){
let nextX = journey[i].y;//?SWITCHED X AND Y AS ASTAR SWITCHES IN GRAPH
let nextY = journey[i].x;//?SWITCHED X AND Y AS ASTAR SWITCHES IN GRAPH
let fromTop = (26 + (nextX * 16) + (nextY * 16)) + "px";
let fromLeft = (576 + (nextX * 32) - (nextY * 32)) + "px";
document.getElementById("playerLayer").style.left = fromLeft;
document.getElementById("playerLayer").style.top = fromTop;
if (i === journey.length - 1) {
resolve("complete")
}
}, i * (900));
}(i));
}
})
}

recursive method in Vue JS

I am attempting to call a method within itself in Vue, however I'm getting the following error
this.loop is not a function. (In 'this.loop()', 'this.stagger'
is undefined)
here is the method:
loop: function () {
var n = $(".item").length;
var i = 1;
var m = n + 5;
setTimeout( function () {
$('.item:nth-child('+i+')').addClass('show');
var x = i - 2;
var y = x - 2;
i = i + 3;
// for 2 columns:
// x = i - 1;
// i = i + 2;
$('.item:nth-child('+x+')').addClass('show');
$('.item:nth-child('+y+')').addClass('show'); // remove for 2 columns
if (i < m) {
this.loop() // error occurs here
}
}, 100)
}
This is because this no longer refers to the object when in the callback function of setTimeout. There are several solutions.
You could change the function to an arrow function:
setTimeout( () => {
That way this will retain its original value, also within the callback.
Or, you could bind this to the function:
setTimeout( function () {
// ...
}.bind(this), 100)
//^^^^
Or, you could copy thisand use that instead:
var that = this;
setTimeout( function () {
// ...
that.loop();
// ...
}, 100)
Avoiding reinitialisation
Currently your recursive calls will also reset the variables, including i.
Solve this, by passing i as argument:
loop: function (i = 1) { // <<---- default value
var n = $(".item").length;
var m = n + 5;
if (i >= m) return; // <<-----
setTimeout(() => {
$('.item:nth-child('+i+')').addClass('show');
var x = i - 2;
var y = x - 2;
$('.item:nth-child('+x+')').addClass('show');
$('.item:nth-child('+y+')').addClass('show');
this.loop(i+3); // <<------
}, 100);
}

Sum of range in Javascript in function local variable

I have range function and output functions they works correct,now I want create sum function for using as callbac function in range function,but when some function executed local variable let us say total or sum initialize 0(zero),how can solve this problem?
function range(start,end,callback,step) {
// body...
step=step || 1;
for(i=start;i<=end;i=i+step){
callback(i);
}
}
function output(a) {
// body...
console.log(a);
}
function sum(m){
var total=0;
// some code
}
range(1,5,output);
range(1,5,sum);
function range(start,end,callback,step) {
// body...
var aggregate;
step=step || 1;
for(i=start;i<=end;i=i+step){
aggregate = callback(i, aggregate);
}
}
function output(a) {
// body...
console.log(a);
}
function sum(m, aggregate){
return m + aggregate;
}
range(1,5,output);
range(1,5,sum);
This way you could even do cool stuff like
function conc(m, aggregate) {
return aggregate + m.toString();
}
range(1,5,conc,2); //prints 135
Continuition style code, like you've started it with range(), can get really weird and cumbersome.
And please, please, mind defining your local variables. like i
function range(start,end,callback,step) {
step=step || 1;
for(var i=start; i<=end; i=i+step)
callback(i);
}
function output(...label) {
return function(...args){
console.log(...label, ...args);
}
}
function sum(callback){
var total = 0;
return function(value){
//will log ever intermediate total, because sum() has no way to tell when the sequence is over.
callback(total += +value || 0);
}
}
range(1,5,output('range:'));
range(1,5,sum(output('sum:')));
In this case, I'd prefer using a generator instead, although the higher order functions get obsolete.
function *range(start,end,step) {
step = +step || (end < start? -1: 1);
for(var value = start, count = (end - start) / step; count-- >= 0; value += step)
yield value
}
function sum(iterator){
var total = 0, v;
for(v of iterator) total += +v || 0;
return total;
}
console.log("range:", ...range(1,5))
console.log("sum of range:", sum(range(1,5)))
//just to show that this works with your regular array as well
console.log("sum of array:", sum([1,2,3,4,5]));
//and some candy, as requested by Bergi ;)
//I like to stay with the interfaces as close as possible to the native ones
//in this case Array#reduce
var fold = (iterator, callback, start = undefined) => {
var initialized = start !== undefined,
acc = start,
index = 0,
value;
for(value of iterator){
acc = initialized?
callback(acc, value, index):
(initialized=true, value);
++index;
}
if(!initialized){
throw new TypeError("fold of empty sequence with no initial value");
}
return acc;
}
//and the ability to compose utility-functions
fold.map = (callback, start = undefined) => iterator => fold(iterator, callback, start);
console.log(" ");
var add = (a,b) => a + b; //a little helper
console.log('using fold:', fold(range(1,5), add, 0));
//a composed utility-function
var sum2 = fold.map(add, 0);
console.log('sum2:', sum2( range(1,5) ));
Clearly a range function should not take a callback but be a generator function in modern JavaScript, however you were asking how to write such a callback.
You've already tagged your questions with closures, and they are indeed the way to go here. By initialising a new total within each call of the outer function, you don't need to worry about how to reset a global counter.
function makeSum() {
var total=0;
return function(m) {
total += m;
return total; // so that we can access the result
}
}
var sum = makeSum();
range(1, 5, sum);
output(sum(0));
Won't simply calling the callback on the range array suffice if the callback is not undefined? Like this:
> function range(n, callback) {
const r = [...Array(n).keys()]
if (callback) {
return callback(r)
}
return r
}
> function sum(arr) {
return arr.reduce((a, b) => a + b, 0)
}
> range(10)
> [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
> range(10, sum)
> 45

How to make a good recurring function in js

I'm trying to make a fallback function that imitates Promise for ie and whatnot
I have the following code:
function goPromise(nr){
console.time("promise");
var sum = 0;
var prom = function(){
return new Promise(function(resolve){
sum = sum + nr;
nr = nr-1;
resolve();
});
}
var doThat = function(){
if(nr > 0){
prom().then(function(){
nr = nr - 1;
doThat()
})
}
else {
console.log(sum);
console.timeEnd("promise");
}
}
doThat();
}
function goNormal(nr){
console.time("normal");
var sum = 0;
var x = function(){
if(nr > 0){
sum = sum + nr;
nr = nr -1;
x();
}
else {
console.timeEnd("normal")
}
}
x();
}
The goNormal works fine and faster than goPromise. That until i give it a big number like 50.000. In which case it gives me this
What does promise have that it can do this stuff no matter how many times?
And how can I implement it in vanilla js ?
A promise is a callback in the future, thus it does NOT recurse. It's like using window.setTimeout (which would solve your issue in the goNormal instance). In the case of the promise, you've set it up and thus the current execution of the "doThat" function actually terminates.
As there is a promise involved, which results in a callback in the future, you'd expect it to be slower. You'll find that using setTimeout will also slow your execution plan down.
The example given is contrived as recursion is not required, however, I get your point. Is there a particular problem you're trying to solve or is this academic?
See my example code below with setTimeout.
function goNormal(nr){
console.time("normal");
var sum = 0;
var x = function(){
if(nr > 0){
sum = sum + nr;
nr = nr -1;
window.setTimeout(x, 10);
}
else {
console.timeEnd("normal")
}
}
x();
}
You can use requestAnimationFrame in order to call the function again, and again, and again ... at roughly 60fps intervals ... and cause it to stop until it meets a certain condition. See simple example:
function recurring() {
var t1 = performance.now();
if (t1 > 1000) {
console.log("Done!");
} else {
console.log("Still going ... ");
window.requestAnimationFrame(recurring);
}
}
window.requestAnimationFrame(recurring);
This post offers two nice solutions that allow you to execute a recursive function as if it was a simple loop.
The first one, the "Trampoline" method:
function trampoline(cb) {
while (cb && cb instanceof Function) {
cb = cb();
}
return cb;
};
function goNormalT(nr) {
console.time("normal");
var sum = 0;
var x = function(){
if(nr > 0){
sum = sum + nr;
nr = nr -1;
return x.bind(null, nr);
}
else {
console.timeEnd("normal");
return null;
}
}
return trampoline(x.bind(null));
};
goNormalT(50000);
And a second one, a "Tail Call Optimizer":
function tco(f) {
var value;
var active = false;
var accumulated = [];
return function accumulator() {
accumulated.push(arguments);
if (!active) {
active = true;
while (accumulated.length) {
value = f.apply(this, accumulated.shift());
}
active = false;
return value;
}
}
}
function goNormal(nr) {
console.time("normal");
var sum = 0;
var x = tco(function() {
if (nr > 0) {
sum = sum + nr;
nr = nr - 1;
return x();
} else {
console.timeEnd("normal");
return null;
}
});
return x();
};
goNormal(50000);
The second one being a bit more sophisticated implementation of the first method.
If the ways these work are hard to grasp, make sure to check the article for an in depth explanation.

Categories

Resources