promise change global variable in for loop - javascript

I am trying to do some benchmarking on different style of javascript code, here is what I have:
var Promise = require('bluebird');
var timer = function(name) {
var start = new Date();
return {
stop: function() {
var end = new Date();
var time = end.getTime() - start.getTime();
console.log('Function:', name, 'finished in', time, 'ms');
}
};
};
function regular (a, cb) {
if (a % 2 === 0) return cb(null, a);
return cb(a);
}
function promise (a) {
return new Promise (function (resolve, reject) {
if (a % 2 === 0) resolve(a);
else reject(a);
});
}
var t = timer('regular');
var g = 0;
for (var i = 1; i < 10001; i++) {
regular(i, function(odd, even) {
if (odd) g = g + (odd * 2);
else g = g + (even * 3);
});
}
console.log('g:', g); // g: 125015000
t.stop();
var t2 = timer('promise');
var h = 0;
for (var i = 1; i < 10001; i++) {
promise(i).then(function(x) {h = h + (x*3);}).catch(function(x) {h = h + (x*2);});
}
console.log('h:', h); // h: 0
t2.stop();
What interesting is, the promises doesn't change the global variable 'h', how can I make it return the same result as variable 'g'?
UPDATE
Here is changed code that try to get the final result, but the non-deterministic of promises give us unexpected result.
for (var i = 1; i < 10001; i++) {
promise(i).then(function(x) {
h = h + (x*3); if (x===10000) console.log('h:', h);
}).catch(function(x) {h = h + (x*2);}); // h: 75015000
}
Currently my code that give the expected output is even stranger.
for (var i = 1; i < 10001; i++) {
promise(i).then(function(x) {
h = h + (x*3);
}).catch(function(x) {
h = h + (x*2);
if (x===9999) console.log('h:', h); // <- attention here
}); // h: 125015000
}
Can anyone show me a better code and explain the code above? (The code above shows the deterministic correct result when i is 9999 not 10000)

Promises ALWAYS call their .then() or .catch() handlers asynchronously. Even if they are resolved immediately, they will let the current thread of JS finish executing and will call the .then() or .catch() handlers asynchronously on the "next tick".
Thus your console.log('h:', h); is executed BEFORE any of then .then() handlers from the loop have been called. If you put the console.log() statement inside the .then() and .catch() handlers, you will find they are getting called, but AFTER your console.log('h:', h); is called.
Promises are designed to be an asynchronous interface. And, it's very important that an asynchronous interface is always consistent so even if the promise is resolved synchronously, they still call their .then() and .catch() handlers asynchronously on the next tick so that they are always consistent and the developer using them doesn't need to worry about sometimes getting an async response and sometimes getting a sync response. Instead, they are always async responses.
As I said in my comment, any real world coding situation should only use promises for operations that are at least sometimes asynchronous. If your operation is always synchronous (as your above example is), then they should not use promises because promises just make synchronous operations more complicated than just using straight synchronous function calls.
In addition, your multiple promise operations in your loop are not sequenced or coordinated in any given way. If these were real async operations, they could complete in any order and your updating of the h variable would have an uncertain order (which is often a problem and thus is usually a bad design pattern).

To solve your problem with promises you can wrap all the then methods with reduce and listen to all Promises finish, this is running the promises sequentially.
if you like you can use also Promise.all() to run in all the promises in parallel.
function promise (a) {
return new Promise (function (resolve, reject) {
if (a % 2 === 0) resolve(a);
else reject(a);
});
};
var timer = function(name) {
var start = new Date();
return {
stop: function() {
var end = new Date();
var time = end.getTime() - start.getTime();
console.log('Function:', name, 'finished in', time, 'ms');
}
};
};
console.log('Right result', Array.from({length : 10000}, (el, i)=> (i + 1) % 2 === 0 ? (i+1)*3 : (i+1)*2).reduce((a,b)=> a + b));
var t2 = timer('promise');
var h = 0;
Promise.all(Array.from({length : 10000}, (el, i)=> promise(i + 1).then(x =>{ h += x*3}).catch(x =>{ h += x*2 })
))
.then(()=>{
console.log('h:', h);
t2.stop();
});

Related

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

How to handle async/await calls inside a setTimeout() block?

I have a process that runs at a specific interval, let's say it runs on the every 10th second of the minute. It is a recursive function so at the end of every 10 seconds, it will call itself. This is working so far.
Inside this timer function, I have an async/await call to an API. Basically what it does is, it makes the API call, gets the data and that data is pushed to a client once it is %100 complete.
The problem is that if that async/await call takes longer than the interval of my timer, I am starting to get overlaps.
So, this is a bit of a design question. I have found the solution by using a global variable inside my API call wrapper function.
var REQUEST_CYCLE_ACTIVE = false;
or
var REQUEST_CYCLE_ACTIVE = true;
Inside the timer, I can skip the recursion if the API call is unfinished and it will try on the next interval.
This works. But I am curios to know if there is a more elegant way to solve this. I am also providing an example node.js application which demonstrates the issue and the solution.
// A simple function to simulate resource delays.
const delay = (ms) => new Promise(resolve => setTimeout(resolve, ms));
// Construct a time string for debug purposes.
const getTime = () => {
var now = new Date();
var h = now.getHours().toString().padStart(2, '0');
var m = now.getMinutes().toString().padStart(2, '0');
var s = now.getSeconds().toString().padStart(2, '0');
const timeSignature = h + ':' + m + ':' + s;
return timeSignature;
}
// Update
// This is the request wrapper.
const update = async () => {
REQUEST_CYCLE_ACTIVE = true;
let result;
try {
result = await someFakeAPI();
} catch (err) {
throw err;
}
let timeSignature = getTime();
console.log(`\t\t(REQUEST) update() is done. Here is the result:`);
console.log('\t\t', result)
// Once the data is %100 collected here (there are retries etc.)
// we make a call to the websocket and force-push the data to all
// of the connected clients.
// PUSH TO CLIENTS()
REQUEST_CYCLE_ACTIVE = false;
};
// The core mock API call function.
async function someFakeAPI() {
// Emulate an asynchroneous fetch.
console.log('\t\t(REQUEST) Fetching data ...')
// 12000 miliseconds (12 sec) is longer than our timer call.
// There will be an overlap.
await delay(12000);
let result = 0.6; // Change to 0.4 to trigger a failed fetch.
if (result < 0.5) {;
return '__FAIL__';
} else {
return {name: 'apple', price: '1234.12', time: 1549926859970};
}
}
// Just an initial API call to get things started.
async function sendExchangeRequest()
{
let result;
try {
result = await someFakeAPI();
} catch (err) {
throw err;
}
return result;
}
// runClock {{{1
const runClock2 = async () => {
let next_update_seconds;
// This is our interval, the recursive function will be called on every n'th second of the minute.
// For example, an interval of 10 means, the calls will be placed at 22:11:00, 22:11:10, 22:11:20 etc.
var interval = 10; //seconds
var date = new Date();
let current_seconds = date.getSeconds();
let buffer = 60 % interval;
let border = (60 - buffer);
// Interval calculations.
if (current_seconds <= border) {
if ((current_seconds % interval) == 0) {
next_update_seconds = interval;
} else {
let distance = (border - current_seconds);
let slice = (distance % interval);
next_update_seconds = slice;
}
} else {
next_update_seconds = (60 - current_seconds);
}
let next_update_milliseconds = (next_update_seconds * 1000);
var timeToNextTick = (next_update_milliseconds);
let timeSignature = getTime();
console.log(`[4] <${timeSignature}> Starting runClock(), time to next Async REQUEST: ${timeToNextTick}ms`);
setTimeout(function() {
let timeSignature = getTime();
console.log(`\t(${timeSignature}) Time is up, done waiting. Async REQUEST will be called.`);
if(REQUEST_CYCLE_ACTIVE){
console.log(`\t(${timeSignature}) Previous Async REQUEST already running. Skipping ...`);
}else{
console.log(`\t(${timeSignature}) Previous Async REQUEST is complete. Running a new one ...`);
// MAKE THE ASYNC API CALL HERE.
update();
}
// Recursion
console.log(`\t(${timeSignature}) Start runClock() again.`);
runClock2();
}, timeToNextTick);
};
var REQUEST_CYCLE_ACTIVE = false;
(async () => {
console.log('[1] Make the request:')
try {
response = await sendExchangeRequest();
console.log('[2] Report the result:\n\t', response)
console.log('[3] Start the clock cycle.');
runClock2();
} catch(error) {
console.log('FAILED:', error)
}
})();
If you have issues with the included code, here is the REPL link: AsyncConcepts2.repl.run
Finally, a gif demonstrating the example:

How can I use a javascript promise to make one function wait for another to finish running asynchronously?

// TODO: FIX CODE - IT IS STILL NOT WAITING FOR populateVenueIDs
// BEFORE CALLING populateFsPhotoRequestURLs
// must use async, not sync to meet assignment requirements.
let populateVenueIDs = new Promise(function(resolve, reject) {
for (let y = 0; y < window.coffeeShopLocations().length; y++) {
let getVenueIDFromFS = new XMLHttpRequest();
getVenueIDFromFS.open('GET', window.fsURL[y]);
getVenueIDFromFS.onload = function() {
let responseFromFS = JSON.parse(getVenueIDFromFS.responseText);
window.fsVenueID[y] = responseFromFS.response.venues[0].id;
console.log(window.fsVenueID[y]);
};
getVenueIDFromFS.send();
}
resolve("done!");
});
function populateFsPhotoRequestURLs() {
for (let y = 0; y < window.coffeeShopLocations().length; y++) {
window.fsPhotoEndpoint[y] = 'https://api.foursquare.com/v2/venues/'
+ window.fsVenueID[y] + '/photos';
window.fsPhotoRequestURL[y] = fsPhotoEndpoint + '?' + fsPhotoParams;
console.log(window.fsPhotoRequestURL[y]);
}
}
populateVenueIDs.then(
populateFsPhotoRequestURLs()
);
It is still not waiting for populateVenueIDs before running populateFsPotoRequestURLs.
I have also tried variants of await, promise, and then. I have read about 15 tutorials about those topics, but none are close enough to do what I want. I don't want to use a timeout (which 90% of tutorials use) because that would unnecessarily slow down the application. I have to use async to meet the assignment requirements.
Please help me connect the dots to apply a promise and/or await/then that will apply in this situation.
Your populateVenueIDs function just resolves to done without waiting for the onload functions to be called. If you can, fetch would be a nice alternative because it returns promises but that isn't supported in IE.
What you can do, is create a promise for each iteration of your loop and use Promise.all(arrayOfPromises) to wait for all requests. This will even execute all XMLHttpRequest in parallel!
Example code:
let populateVenueIDs = new Promise(function(resolve, reject) {
let promises = [];
for (let y = 0; y < window.coffeeShopLocations().length; y++) {
promises.push(new Promise(function (resolve) {
let getVenueIDFromFS = new XMLHttpRequest();
getVenueIDFromFS.open('GET', window.fsURL[y]);
getVenueIDFromFS.onload = function() {
let responseFromFS = JSON.parse(getVenueIDFromFS.responseText);
window.fsVenueID[y] = responseFromFS.response.venues[0].id;
console.log(window.fsVenueID[y]);
resolve();
};
getVenueIDFromFS.send();
}));
}
return Promise.all(promises)
.then(function () {
resolve('Done!');
})
});
Fetch Solution
As you've said this is an assignment, you probably can use ES6 with async and await so this would be a fancy solution:
async function populateVenueIDs() {
let promises = [];
for (let y = 0; y < window.coffeeShopLocations().length; y++) {
promises.push(new Promise(async resolve => {
const response = await fetch(window.fsURL[y]);
const data = await response.json();
window.fsVenueID[y] = data.venues[0].id;
resolve();
}));
}
await Promise.all(promises);
}
While I feel strongly that #JensV had the right answer, and I said so in my comment, I just wanted to follow up on what your code might look like if you used fetch (I only included the relevant piece of JensV's answer.
var promises = [];
for (let y = 0; y < window.coffeeShopLocations().length; y++) {
promises.push(fetch(window.fsURL[y])
.then(response => response.json())
.then(data => {
window.fsVenueID[y] = data.response.venues[0].id;
}));
}
EDIT: For posterity's sake I posted this before Jens added his fetch solution :)

Bluebird promise: why it doesn't timeout?

So, I'm trying to model some long computation. for this purpose I'm computing the fibonacci number. In case when computation takes to much time I need to reject it.
The question: why TimeoutErrror handler doesn't work? How to fix the code?
const expect = require('chai').expect
const Promise = require('bluebird')
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return profib(n, cur, strAdd(cur, prev));
}
})
}
const TIMEOUT = 10000
const N = 20000
describe('recursion', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
let prom = profib(N).timeout(1000)
.catch(Promise.TimeoutError, function(e) {
console.log('timeout', e)
return '-1'
})
return prom.then((num) => {
expect(num).equal('-1')
})
})
})
const strAdd = function(lnum, rnum) {
lnum = lnum.split('').reverse();
rnum = rnum.split('').reverse();
var len = Math.max(lnum.length, rnum.length),
acc = 0;
res = [];
for(var i = 0; i < len; i++) {
var subres = Number(lnum[i] || 0) + Number(rnum[i] || 0) + acc;
acc = ~~(subres / 10); // integer division
res.push(subres % 10);
}
if (acc !== 0) {
res.push(acc);
}
return res.reverse().join('');
};
Some info about environment:
➜ node -v
v6.3.1
➜ npm list --depth=0
├── bluebird#3.4.6
├── chai#3.5.0
└── mocha#3.2.0
If I'm reading your code correctly profib does not exit until it's finished.
Timeouts are not interrupts. They are just events added to the list of events for the browser/node to run. The browser/node runs the next event when the code for the current event finishes.
Example:
setTimeout(function() {
console.log("timeout");
}, 1);
for(var i = 0; i < 100000; ++i) {
console.log(i);
}
Even though the timeout is set for 1 millisecond it doesn't appear until after the loop finishes (Which takes about 5 seconds on my machine)
You can see the same problem with a simple forever loop
const TIMEOUT = 10000
describe('forever', function() {
it.only('cancelation', function() {
this.timeout(2 * TIMEOUT)
while(true) { } // loop forever
})
})
Run in with your environment and you'll see it never times out. JavaScript does not support interrupts, it only supports events.
As for fixing the code you need to insert a call to setTimeout. For example, let's change forever loop so it exits (and therefore allows other events)
const TIMEOUT = 100
function alongtime(n) {
return new Promise(function(resolve, reject) {
function loopTillDone() {
if (n) {
--n;
setTimeout(loopTillDone);
} else {
resolve();
}
}
loopTillDone();
});
}
describe('forever', function() {
it.only('cancelation', function(done) {
this.timeout(2 * TIMEOUT)
alongtime(100000000).then(done);
})
})
Unfortunately using setTimeout is really a slow operation and arguably shouldn't be used in a function like profib. I don't really know what to suggest.
The problem appears because promises work in a "greedy" manner(it's my own explanation). For this reason function profib doesn't release event loop. To fix this issue I need to release event loop. The easiest way to do that with Promise.delay():
function profib(n, prev = '0', cur = '1') {
return new Promise.resolve(n < 2)
.then(function(isTerm) {
if(isTerm) {
return cur
} else {
n = n - 2
return Promise.delay(0).then(() => profib(n, cur, strAdd(cur, prev));
}
})
}
gman has already explained why your idea doesn't work. The simple and efficient solution would be to add a condition in your loop that checks the time and breaks, like thus :
var deadline = Date.now() + TIMEOUT
function profib(n, prev = '0', cur = '1') {
if (Date.now() >= deadline) throw new Error("timed out")
// your regular fib recursion here
}
Calling profib will either eventually return the result, or throw an error. However, it will block any other JavaScript from running while doing the calculation. Asynchronous execution isn't the solution here. Or at least, not all of it. What you need for such CPU-intensive tasks is a WebWorker to run it in another JavaScript context. Then you can wrap your WebWorker's communication channel in a Promise to get the API you envisioned originally.

Asynchronous for cycle in JavaScript

I need a loop that waits for an async call before continuing. Something like:
for ( /* ... */ ) {
someFunction(param1, praram2, function(result) {
// Okay, for cycle could continue
})
}
alert("For cycle ended");
How could I do this? Do you have any ideas?
You can't mix synchronous and asynchronous in JavaScript if you block the script, you block the Browser.
You need to go the full event driven way here, luckily we can hide the ugly stuff away.
EDIT: Updated the code.
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;
}
This will provide us an asynchronous loop, you can of course modify it even further to take for example a function to check the loop condition etc.
Now on to the test:
function someFunction(a, b, callback) {
console.log('Hey doing some stuff!');
callback();
}
asyncLoop(10, function(loop) {
someFunction(1, 2, function(result) {
// log the iteration
console.log(loop.iteration());
// Okay, for cycle could continue
loop.next();
})},
function(){console.log('cycle ended')}
);
And the output:
Hey doing some stuff!
0
Hey doing some stuff!
1
Hey doing some stuff!
2
Hey doing some stuff!
3
Hey doing some stuff!
4
Hey doing some stuff!
5
Hey doing some stuff!
6
Hey doing some stuff!
7
Hey doing some stuff!
8
Hey doing some stuff!
9
cycle ended
I simplified this:
FUNCTION:
var asyncLoop = function(o){
var i=-1;
var loop = function(){
i++;
if(i==o.length){o.callback(); return;}
o.functionToLoop(loop, i);
}
loop();//init
}
USAGE:
asyncLoop({
length : 5,
functionToLoop : function(loop, i){
setTimeout(function(){
document.write('Iteration ' + i + ' <br>');
loop();
},1000);
},
callback : function(){
document.write('All done!');
}
});
EXAMPLE: http://jsfiddle.net/NXTv7/8/
A cleaner alternative to what #Ivo has suggested would be an Asynchronous Method Queue, assuming that you only need to make one async call for the collection.
(See this post by Dustin Diaz for a more detailed explanation)
function Queue() {
this._methods = [];
this._response = null;
this._flushed = false;
}
(function(Q){
Q.add = function (fn) {
if (this._flushed) fn(this._response);
else this._methods.push(fn);
}
Q.flush = function (response) {
if (this._flushed) return;
this._response = response;
while (this._methods[0]) {
this._methods.shift()(response);
}
this._flushed = true;
}
})(Queue.prototype);
You simply create a new instance of Queue, add the callbacks you need, and then flush the queue with the async response.
var queue = new Queue();
queue.add(function(results){
for (var result in results) {
// normal loop operation here
}
});
someFunction(param1, param2, function(results) {
queue.flush(results);
}
An added benefit of this pattern is that you can add multiple functions to the queue instead of just one.
If you have an object which contains iterator functions, you can add support for this queue behind the scenes and write code which looks synchronous, but isn't:
MyClass.each(function(result){ ... })
simply write each to put the anonymous function into the queue instead of executing it immediately, and then flush the queue when your async call is complete. This is a very simple and powerful design pattern.
P.S. If you're using jQuery, you already have an async method queue at your disposal called jQuery.Deferred.
Also look at this splendid library caolan / async. Your for loop can easily be accomplished using mapSeries or series.
I could post some sample code if your example had more details in it.
We can also use help of jquery.Deferred. in this case asyncLoop function would look like this:
asyncLoop = function(array, callback) {
var nextElement, thisIteration;
if (array.length > 0) nextElement = array.pop();
thisIteration = callback(nextElement);
$.when(thisIteration).done(function(response) {
// here we can check value of response in order to break or whatever
if (array.length > 0) asyncLoop(array, collection, callback);
});
};
the callback function will look like this:
addEntry = function(newEntry) {
var deferred, duplicateEntry;
// on the next line we can perform some check, which may cause async response.
duplicateEntry = someCheckHere();
if (duplicateEntry === true) {
deferred = $.Deferred();
// here we launch some other function (e.g. $.ajax or popup window)
// which based on result must call deferred.resolve([opt args - response])
// when deferred.resolve is called "asyncLoop" will start new iteration
// example function:
exampleFunction(duplicateEntry, deferred);
return deferred;
} else {
return someActionIfNotDuplicate();
}
};
example function that resolves deferred:
function exampleFunction(entry, deffered){
openModal({
title: "what should we do with duplicate"
options: [
{name:"Replace", action: function(){replace(entry);deffered.resolve(replace:true)}},
{name: "Keep Existing", action: function(){deffered.resolve(replace:false)}}
]
})
}
I have been using the "setTimeout(Func,0);" trick for about year. Here is some recent research i wrote up to explain how to speed it up a bit. If you just want the answer, skip to Step 4. Step 1 2 and 3 explain the reasoning and mechanics;
// In Depth Analysis of the setTimeout(Func,0) trick.
//////// setTimeout(Func,0) Step 1 ////////////
// setTimeout and setInterval impose a minimum
// time limit of about 2 to 10 milliseconds.
console.log("start");
var workCounter=0;
var WorkHard = function()
{
if(workCounter>=2000) {console.log("done"); return;}
workCounter++;
setTimeout(WorkHard,0);
};
// this take about 9 seconds
// that works out to be about 4.5ms per iteration
// Now there is a subtle rule here that you can tweak
// This minimum is counted from the time the setTimeout was executed.
// THEREFORE:
console.log("start");
var workCounter=0;
var WorkHard = function()
{
if(workCounter>=2000) {console.log("done"); return;}
setTimeout(WorkHard,0);
workCounter++;
};
// This code is slightly faster because we register the setTimeout
// a line of code earlier. Actually, the speed difference is immesurable
// in this case, but the concept is true. Step 2 shows a measurable example.
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 2 ////////////
// Here is a measurable example of the concept covered in Step 1.
var StartWork = function()
{
console.log("start");
var startTime = new Date();
var workCounter=0;
var sum=0;
var WorkHard = function()
{
if(workCounter>=2000)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: sum=" + sum + " time=" + ms + "ms");
return;
}
for(var i=0; i<1500000; i++) {sum++;}
workCounter++;
setTimeout(WorkHard,0);
};
WorkHard();
};
// This adds some difficulty to the work instead of just incrementing a number
// This prints "done: sum=3000000000 time=18809ms".
// So it took 18.8 seconds.
var StartWork = function()
{
console.log("start");
var startTime = new Date();
var workCounter=0;
var sum=0;
var WorkHard = function()
{
if(workCounter>=2000)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: sum=" + sum + " time=" + ms + "ms");
return;
}
setTimeout(WorkHard,0);
for(var i=0; i<1500000; i++) {sum++;}
workCounter++;
};
WorkHard();
};
// Now, as we planned, we move the setTimeout to before the difficult part
// This prints: "done: sum=3000000000 time=12680ms"
// So it took 12.6 seconds. With a little math, (18.8-12.6)/2000 = 3.1ms
// We have effectively shaved off 3.1ms of the original 4.5ms of dead time.
// Assuming some of that time may be attributed to function calls and variable
// instantiations, we have eliminated the wait time imposed by setTimeout.
// LESSON LEARNED: If you want to use the setTimeout(Func,0) trick with high
// performance in mind, make sure your function takes more than 4.5ms, and set
// the next timeout at the start of your function, instead of the end.
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 3 ////////////
// The results of Step 2 are very educational, but it doesn't really tell us how to apply the
// concept to the real world. Step 2 says "make sure your function takes more than 4.5ms".
// No one makes functions that take 4.5ms. Functions either take a few microseconds,
// or several seconds, or several minutes. This magic 4.5ms is unattainable.
// To solve the problem, we introduce the concept of "Burn Time".
// Lets assume that you can break up your difficult function into pieces that take
// a few milliseconds or less to complete. Then the concept of Burn Time says,
// "crunch several of the individual pieces until we reach 4.5ms, then exit"
// Step 1 shows a function that is asyncronous, but takes 9 seconds to run. In reality
// we could have easilly incremented workCounter 2000 times in under a millisecond.
// So, duh, that should not be made asyncronous, its horrible. But what if you don't know
// how many times you need to increment the number, maybe you need to run the loop 20 times,
// maybe you need to run the loop 2 billion times.
console.log("start");
var startTime = new Date();
var workCounter=0;
for(var i=0; i<2000000000; i++) // 2 billion
{
workCounter++;
}
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
// prints: "done: workCounter=2000000000 time=7214ms"
// So it took 7.2 seconds. Can we break this up into smaller pieces? Yes.
// I know, this is a retarded example, bear with me.
console.log("start");
var startTime = new Date();
var workCounter=0;
var each = function()
{
workCounter++;
};
for(var i=0; i<20000000; i++) // 20 million
{
each();
}
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
// The easiest way is to break it up into 2 billion smaller pieces, each of which take
// only several picoseconds to run. Ok, actually, I am reducing the number from 2 billion
// to 20 million (100x less). Just adding a function call increases the complexity of the loop
// 100 fold. Good lesson for some other topic.
// prints: "done: workCounter=20000000 time=7648ms"
// So it took 7.6 seconds, thats a good starting point.
// Now, lets sprinkle in the async part with the burn concept
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
setTimeout(Work,0);
};
// prints "done: workCounter=20000000 time=107119ms"
// Sweet Jesus, I increased my 7.6 second function to 107.1 seconds.
// But it does prevent the browser from locking up, So i guess thats a plus.
// Again, the actual objective here is just to increment workCounter, so the overhead of all
// the async garbage is huge in comparison.
// Anyway, Lets start by taking advice from Step 2 and move the setTimeout above the hard part.
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
if(index>=end) {return;}
setTimeout(Work,0);
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 4.5); // burnTimeout set to 4.5ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
};
// This means we also have to check index right away because the last iteration will have nothing to do
// prints "done: workCounter=20000000 time=52892ms"
// So, it took 52.8 seconds. Improvement, but way slower than the native 7.6 seconds.
// The Burn Time is the number you tweak to get a nice balance between native loop speed
// and browser responsiveness. Lets change it from 4.5ms to 50ms, because we don't really need faster
// than 50ms gui response.
console.log("start");
var startTime = new Date();
var workCounter=0;
var index=0;
var end = 20000000;
var each = function()
{
workCounter++;
};
var Work = function()
{
if(index>=end) {return;}
setTimeout(Work,0);
var burnTimeout = new Date();
burnTimeout.setTime(burnTimeout.getTime() + 50); // burnTimeout set to 50ms in the future
while((new Date()) < burnTimeout)
{
if(index>=end)
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
return;
}
each();
index++;
}
};
// prints "done: workCounter=20000000 time=52272ms"
// So it took 52.2 seconds. No real improvement here which proves that the imposed limits of setTimeout
// have been eliminated as long as the burn time is anything over 4.5ms
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 4 ////////////
// The performance numbers from Step 3 seem pretty grim, but GUI responsiveness is often worth it.
// Here is a short library that embodies these concepts and gives a descent interface.
var WilkesAsyncBurn = function()
{
var Now = function() {return (new Date());};
var CreateFutureDate = function(milliseconds)
{
var t = Now();
t.setTime(t.getTime() + milliseconds);
return t;
};
var For = function(start, end, eachCallback, finalCallback, msBurnTime)
{
var i = start;
var Each = function()
{
if(i==-1) {return;} //always does one last each with nothing to do
setTimeout(Each,0);
var burnTimeout = CreateFutureDate(msBurnTime);
while(Now() < burnTimeout)
{
if(i>=end) {i=-1; finalCallback(); return;}
eachCallback(i);
i++;
}
};
Each();
};
var ForEach = function(array, eachCallback, finalCallback, msBurnTime)
{
var i = 0;
var len = array.length;
var Each = function()
{
if(i==-1) {return;}
setTimeout(Each,0);
var burnTimeout = CreateFutureDate(msBurnTime);
while(Now() < burnTimeout)
{
if(i>=len) {i=-1; finalCallback(array); return;}
eachCallback(i, array[i]);
i++;
}
};
Each();
};
var pub = {};
pub.For = For; //eachCallback(index); finalCallback();
pub.ForEach = ForEach; //eachCallback(index,value); finalCallback(array);
WilkesAsyncBurn = pub;
};
///////////////////////////////////////////////
//////// setTimeout(Func,0) Step 5 ////////////
// Here is an examples of how to use the library from Step 4.
WilkesAsyncBurn(); // Init the library
console.log("start");
var startTime = new Date();
var workCounter=0;
var FuncEach = function()
{
if(workCounter%1000==0)
{
var s = "<div></div>";
var div = jQuery("*[class~=r1]");
div.append(s);
}
workCounter++;
};
var FuncFinal = function()
{
var ms = (new Date()).getTime() - startTime.getTime();
console.log("done: workCounter=" + workCounter + " time=" + ms + "ms");
};
WilkesAsyncBurn.For(0,2000000,FuncEach,FuncFinal,50);
// prints: "done: workCounter=20000000 time=149303ms"
// Also appends a few thousand divs to the html page, about 20 at a time.
// The browser is responsive the entire time, mission accomplished
// LESSON LEARNED: If your code pieces are super tiny, like incrementing a number, or walking through
// an array summing the numbers, then just putting it in an "each" function is going to kill you.
// You can still use the concept here, but your "each" function should also have a for loop in it
// where you burn a few hundred items manually.
///////////////////////////////////////////////
Given an asynchronous worker function someFunction that will call back a result function with a result argument saying whether or not the loop should continue:
// having:
// function someFunction(param1, praram2, resultfunc))
// function done() { alert("For cycle ended"); }
(function(f){ f(f) })(function(f){
someFunction("param1", "praram2", function(result){
if (result)
f(f); // loop continues
else
done(); // loop ends
});
})
In order to check whether or not to end the loop, the worker function someFunction can forward the result function to other asynchronous operations. Also, the whole expression can be encapsulated into an asynchronous function by taking a function done as callback.
If you like wilsonpage's answer but are more accustomed to using async.js's syntax, here is a variation:
function asyncEach(iterableList, callback, done) {
var i = -1,
length = iterableList.length;
function loop() {
i++;
if (i === length) {
done();
return;
}
callback(iterableList[i], loop);
}
loop();
}
asyncEach(['A', 'B', 'C'], function(item, callback) {
setTimeout(function(){
document.write('Iteration ' + item + ' <br>');
callback();
}, 1000);
}, function() {
document.write('All done!');
});
Demo can be found here - http://jsfiddle.net/NXTv7/8/
Here's another example which I think is more readable than others, where you wrap your async function inside a function that takes in a done function, the current loop index, and the result (if any) of the previous async call:
function (done, i, prevResult) {
// perform async stuff
// call "done(result)" in async callback
// or after promise resolves
}
Once done() is invoked, it triggers the next async call, again passing in the done function, current index and previous result. Once the entire loop is completed, the provided loop callback will be invoked.
Here's a snippet you can run:
asyncLoop({
limit: 25,
asyncLoopFunction: function(done, i, prevResult) {
setTimeout(function() {
console.log("Starting Iteration: ", i);
console.log("Previous Result: ", prevResult);
var result = i * 100;
done(result);
}, 1000);
},
initialArgs: 'Hello',
callback: function(result) {
console.log('All Done. Final result: ', result);
}
});
function asyncLoop(obj) {
var limit = obj.limit,
asyncLoopFunction = obj.asyncLoopFunction,
initialArgs = obj.initialArgs || {},
callback = obj.callback,
i = 0;
function done(result) {
i++;
if (i < limit) {
triggerAsync(result);
} else {
callback(result);
}
}
function triggerAsync(prevResult) {
asyncLoopFunction(done, i, prevResult);
}
triggerAsync(initialArgs); // init
}
You can use async await introduced in ES7:
for ( /* ... */ ) {
let result = await someFunction(param1, param2);
}
alert("For cycle ended");
This works only if someFunction is returning a Promise!
If someFunction is not returning a Promise, then you can make it return a Promise by yourself like this:
function asyncSomeFunction(param1,praram2) {
return new Promise((resolve, reject) => {
someFunction(praram1,praram2,(result)=>{
resolve(result);
})
})
}
Then replace this line await someFunction(param1, param2); by await asynSomeFunction(param1, param2);
Please understand Promises before writing async await code!
http://cuzztuts.blogspot.ro/2011/12/js-async-for-very-cool.html
EDIT:
link from github: https://github.com/cuzzea/lib_repo/blob/master/cuzzea/js/functions/core/async_for.js
function async_for_each(object,settings){
var l=object.length;
settings.limit = settings.limit || Math.round(l/100);
settings.start = settings.start || 0;
settings.timeout = settings.timeout || 1;
for(var i=settings.start;i<l;i++){
if(i-settings.start>=settings.limit){
setTimeout(function(){
settings.start = i;
async_for_each(object,settings)
},settings.timeout);
settings.limit_callback ? settings.limit_callback(i,l) : null;
return false;
}else{
settings.cbk ? settings.cbk(i,object[i]) : null;
}
}
settings.end_cbk?settings.end_cbk():null;
return true;
}
This function allows you to to create a percent break in the for loop using settings.limit. The limit property is just a integer, but when set as array.length * 0.1, this will make the settings.limit_callback to be called every 10%.
/*
* params:
* object: the array to parse
* settings_object:
* cbk: function to call whenwhen object is found in array
* params: i,object[i]
* limit_calback: function to call when limit is reached
* params: i, object_length
* end_cbk: function to call when loop is finished
* params: none
* limit: number of iteration before breacking the for loop
* default: object.length/100
* timeout: time until start of the for loop(ms)
* default: 1
* start: the index from where to start the for loop
* default: 0
*/
exemple:
var a = [];
a.length = 1000;
async_for_each(a,{
limit_callback:function(i,l){console.log("loading %s/%s - %s%",i,l,Math.round(i*100/l))}
});
A promise library based solution:
/*
Since this is an open question for JS I have used Kris Kowal's Q promises for the same
*/
var Q = require('q');
/*
Your LOOP body
#success is a parameter(s) you might pass
*/
var loopBody = function(success) {
var d = Q.defer(); /* OR use your favorite promise library like $q in angular */
/*
'setTimeout' will ideally be your node-like callback with this signature ... (err, data) {}
as shown, on success you should resolve
on failure you should reject (as always ...)
*/
setTimeout(function(err, data) {
if (!err) {
d.resolve('success');
} else {
d.reject('failure');
}
}, 100); //100 ms used for illustration only
return d.promise;
};
/*
function to call your loop body
*/
function loop(itr, fn) {
var def = Q.defer();
if (itr <= 0) {
def.reject({ status: "un-successful " });
} else {
var next = loop.bind(undefined, itr - 1, fn); // 'next' is all there is to this
var callback = fn.bind(undefined /*, a, b, c.... */ ); // in case you want to pass some parameters into your loop body
def.promise = callback().then(def.resolve, next);
}
return def.promise;
}
/*
USAGE: loop(iterations, function(){})
the second argument has to be thenable (in other words return a promise)
NOTE: this loop will stop when loop body resolves to a success
Example: Try to upload file 3 times. HURRAY (if successful) or log failed
*/
loop(4, loopBody).then(function() {
//success handler
console.log('HURRAY')
}, function() {
//failed
console.log('failed');
});
I needed to call some asynchronous function X times, each iteration must have happened after the previous one was done, so I wrote a litte library that can be used like this:
// https://codepen.io/anon/pen/MOvxaX?editors=0012
var loop = AsyncLoop(function(iteration, value){
console.log("Loop called with iteration and value set to: ", iteration, value);
var random = Math.random()*500;
if(random < 200)
return false;
return new Promise(function(resolve){
setTimeout(resolve.bind(null, random), random);
});
})
.finished(function(){
console.log("Loop has ended");
});
Each time user defined loop function is called, it has two arguments, iteration index and previous call return value.
This is an example of output:
"Loop called with iteration and value set to: " 0 null
"Loop called with iteration and value set to: " 1 496.4137048207333
"Loop called with iteration and value set to: " 2 259.6020382449663
"Loop called with iteration and value set to: " 3 485.5400568702862
"Loop has ended"

Categories

Resources