This is my code:
var testStacks = new Array();
function test(elem) {
console.log(elem);
... asynch operations
}
testStacks.push(test("hello 0"));
testStacks.push(test("hello 1"));
testStacks.push(test("hello 2"));
testStacks.push(test("hello 3"));
testStacks.push(test("hello 4"));
// init first 3 functions
testStacks[0];
testStacks[1];
testStacks[2];
and I want to execute 3 functions at time. So hello 0, hello 1 and hello 2 start together at the beginning. Than, once one function finish (they do asynch ops) it must calls the next one (not executed yet) from the array. And so on...
Seems that testStacks[0] does nothing, and when I push the function, it will be executed.
How can I do this? (I want to avoid setInterval()).
A simple approach could be pushing both the function AND the parameters.
var testStacks = new Array();
function test(elem) {
console.log(elem);
... asynch operations
}
testStacks.push({func: test, param: "hello 0"});
testStacks.push({func: test, param: "hello 1"});
testStacks.push({func: test, param: "hello 2"});
testStacks.push({func: test, param: "hello 3"});
testStacks.push({func: test, param: "hello 4"});
// init first 3 functions
testStacks[0].func(testStacks[0].param);
testStacks[1].func(testStacks[1].param);
testStacks[2].func(testStacks[2].param);
This could be generalized and cleaned up in many ways, of course, but should give you a basic idea.
You are executing the function and pushing the return value. Push a function instead:
testStacks.push(function(){ test("hello 0"); });
Whatever solution you'll choose, you'll need something like a third party object to manage the current call stack, and a way to notify this object whenever an operation is completed. Regarding the following (pretty dirty) code, I've decided to use a simple callback called from the test function :
var Stack = function (maxCalls, stack) {
this.ongoing = 0;
this.maxCalls = maxCalls;
Array.prototype.push.apply(this, stack);
this.next(); // starts immediately
};
Stack.prototype = Object.create(Array.prototype);
Stack.prototype.next = function () {
var me = this;
while (this.length && this.ongoing < this.maxCalls) {
this.ongoing++;
// calls the next function
// passing a callback as a parameter
this.shift()(function () {
me.ongoing--;
me.next();
});
}
};
See this demo for a use case : http://jsfiddle.net/wared/5eu8b/. As you can see, functions are called one after the other in a First In First Out way, but they complete in any order.
Hope it can help somehow :)
Related
I have been tasked with writing unit tests for some AngularJS code that was written by another team, who didn't write any tests
They have written the following function but I cannot figure out how to test it
function showCallAlerts(callRecord, isInEditMode, callBack) {
var callAlerts = populateCallAlertOnEditCall(callRecord.callAlert);
var callModalInstance = openAlertModalInstance('Call', callAlerts, callBack);
if (callModalInstance !== undefined && callModalInstance !== null) {
callModalInstance.result.then(function() {
// Show equipment alerts based on company details
showEquipmentAlertsBasedOnCompanyDetails(callRecord, isInEditMode, callBack);
});
} else {
// Show equipment alerts based on company details
showEquipmentAlertsBasedOnCompanyDetails(callRecord, isInEditMode, callBack);
}
}
I need to test that each of the functions are called, not worrying about what they do as I'll test them separate, just that they are called.
When populateCallAlertOnEditCall is called it needs to either return an empty array or an array with some items in it
When openAlertModalInstance is called it needs to either return undefined or something that passes through to showEquipmentAlertsBasedOnCompanyDetails
showEquipmentAlertsBasedOnCompanyDetails should actually be called, I'll test that method separate, just that it was called
I have manged to write code to test simple functions but nothing like this one so any help will be much appreciated, I spent most of this afternoon trying to figure it out
You can use jasmine to mock the function calls that you are not interested in testing. For example, you can tell jasmine to return an empty array every time 'populateCallAlertOnEditCall' is called. I will write an example that might give you an insight:
describe('My Test Spec', function() {
var myController;
...
beforeEach( inject(($controller) => {
myController = $controller("myControllerName");
}));
it('Testing showCallAlerts when populateCallAlertOnEditCall returns an empty array', inject(function($controller) {
//setup
//this will replace every call to populateCallAlertOnEditCall with
//the function inside callFake
spyOn(myController, 'populateCallAlertOnEditCall ').and.callFake(function() {
return []; //returning an empty array.
});
//action
myController.showCallAlerts(...);
//assert
//Do your checking here.
}));
it('Testing showCallAlerts when populateCallAlertOnEditCall returns a non-empty array', inject(function($controller) {
//setup
//this will replace every call to populateCallAlertOnEditCall with
//the function inside callFake
spyOn(myController, 'populateCallAlertOnEditCall ').and.callFake(function() {
return [1,2,3,4]; //returning a non-empty array.
});
//action
myController.showCallAlerts(...);
//assert
//Do your checking here.
}));
});
the test that something has been called, you can use a Spy
your assertion would look like:
spyOn(obj, 'populateCallAlertOnEditCall')
expect(obj.method).toHaveBeenCalled()
UPDATED:
populateCallAlertOnEditCall = {}
spyOn(obj, 'populateCallAlertOnEditCall.result')
expect(obj.method).toHaveBeenCalled()
The kind of behaviour you want is called mocking
In Jasmine, mocking is done with Spy Objects, you can read more about those here
Basically, you can use mocks to test if functions were called with the expected parameters.
var xhr = mock( XMLHttpRequest );
xhr.send();
expect( xhr.send ).toHaveBeenCalled();
I'm trying to create a throttling queue of sorts in a nodeJS module. I'm getting this error back:
timers.js:265
callback.apply(this, args);
^
TypeError: Cannot read property 'apply' of undefined
at wrapper [as _onTimeout] (timers.js:265:13)
at Timer.listOnTimeout (timers.js:110:15)
I'm guessing that I'm doing something stupid, as usual, but is there a reason that it loses the closure scope or something when the second interval runs?
var queueSvc = function(settings){
var queue = ['bob', 'is', 'name', 'my', 'hi'];
var svc = {};
var runtime;
svc.addQuery = function(queueEntry){
queue.push(queueEntry);
};
svc.stopQueue = function(){
clearInterval(runtime);
};
svc.startQueue = function(){
runtime = setInterval(runQueue(queue), settings.queueInterval);
};
svc.emptyQueue = function(){
//This method of emptying the array needs to change
//if we decide to make the queue a public property
queue = [];
};
return svc;
};
function runQueue(queue){
console.log(JSON.stringify(queue));
if(queue.length > 0){
var entry = queue.pop();
console.log(entry);
}
}
var it = queueSvc({queueInterval: 3000});
it.startQueue();
This is a very common mistake. You are running runQueue(queue) immediately and then passing the return value of that to setInterval(). That return value is undefined so what you are doing is essentially this:
runQueue(queue);
setInterval(undefined, settings.queueInterval);
This is obviously, not what you want. Whenever you put () after a function as in runQueue() that means to run it immediately. A function name or definition without () after it is just passing a function reference that can be called later.
So, you need to pass a function reference to setInterval() that can be called LATER like this:
setInterval(function() {
runQueue(queue);
}, settings.queueInteval);
Sometimes people understand it a bit better when you break it out into a named function (not necessary, but helpful in understanding what is going on):
function run() {
runQueue(queue);
}
setInterval(run, settings.queueInteval);
Here you see you are passing only a function reference to setInterval() and letting the timer infrastructure call the function some time later.
The anonymous function in my first code block accomplishes the same thing. It declares a second function that we can pass a reference of to setInterval() and when that is called, it then calls runQueue(queue) with the desired argument.
This line looks very suspcious:
setInterval(runQueue(queue), settings.queueInterval);
runQueue doesn't return a function, in fact it returns nothing (undefined). You probably want:
setInterval(function () {
runQueue(queue);
}, settings.queueInterval);
To supplement existing answers, wrapping runQueue into something callable works. However, you could also restructure your logic entirely.
var queueSvc = function(settings){
var queue = ['bob', 'is', 'name', 'my', 'hi'];
var svc = {};
var runtime;
svc.addQuery = function(queueEntry){
queue.push(queueEntry);
};
svc.stopQueue = function(){
clearInterval(runtime);
};
svc.startQueue = function(){
runtime = setInterval(svc.runQueue, settings.queueInterval);
};
svc.emptyQueue = function(){
//This method of emptying the array needs to change
//if we decide to make the queue a public property
queue = [];
};
svc.runQueue = function() {
console.log(JSON.stringify(queue));
if(queue.length > 0){
var entry = queue.pop();
console.log(entry);
}
};
return svc;
};
var it = queueSvc({queueInterval: 3000});
it.startQueue();
This is exactly what I was looking for, thanks.
Only one issue: you implemented a stack, not a queue.
Queues are FIFO and use push() and shift()
Stacks are LIFO and use push() and pop()
As long as the ordering does not matter, then either is fine.
I've been developing in JavaScript for quite some time but net yet a cowboy developer, as one of the many things that always haunts me is synching JavaScript's callbacks.
I will describe a generic scenario when this concern will be raised: I have a bunch of operations to perform multiple times by a for loop, and each of the operations has a callback. After the for loop, I need to perform another operation but this operation can only execute successfully if all the callbacks from the for loop are done.
Code Example:
for ... in ... {
myFunc1(callback); // callbacks are executed asynchly
}
myFunc2(); // can only execute properly if all the myFunc1 callbacks are done
Suggested Solution:
Initiate a counter at the beginning of the loop holding the length of the loop, and each callback decrements that counter. When the counter hits 0, execute myFunc2. This is essentially to let the callbacks know if it's the last callback in sequence and if it is, call myFunc2 when it's done.
Problems:
A counter is needed for every such sequence in your code, and having meaningless counters everywhere is not a good practice.
If you recall how thread conflicts in classical synchronization problem, when multiple threads are all calling var-- on the same var, undesirable outcomes would occur. Does the same happen in JavaScript?
Ultimate Question:
Is there a better solution?
The good news is that JavaScript is single threaded; this means that solutions will generally work well with "shared" variables, i.e. no mutex locks are required.
If you want to serialize asynch tasks, followed by a completion callback you could use this helper function:
function serializeTasks(arr, fn, done)
{
var current = 0;
fn(function iterate() {
if (++current < arr.length) {
fn(iterate, arr[current]);
} else {
done();
}
}, arr[current]);
}
The first argument is the array of values that needs to be passed in each pass, the second argument is a loop callback (explained below) and the last argument is the completion callback function.
This is the loop callback function:
function loopFn(nextTask, value) {
myFunc1(value, nextTask);
}
The first argument that's passed is a function that will execute the next task, it's meant to be passed to your asynch function. The second argument is the current entry of your array of values.
Let's assume the asynch task looks like this:
function myFunc1(value, callback)
{
console.log(value);
callback();
}
It prints the value and afterwards it invokes the callback; simple.
Then, to set the whole thing in motion:
serializeTasks([1,2, 3], loopFn, function() {
console.log('done');
});
Demo
To parallelize them, you need a different function:
function parallelizeTasks(arr, fn, done)
{
var total = arr.length,
doneTask = function() {
if (--total === 0) {
done();
}
};
arr.forEach(function(value) {
fn(doneTask, value);
});
}
And your loop function will be this (only parameter name changes):
function loopFn(doneTask, value) {
myFunc1(value, doneTask);
}
Demo
The second problem is not really a problem as long as every one of those is in a separate function and the variable is declared correctly (with var); local variables in functions do not interfere with each other.
The first problem is a bit more of a problem. Other people have gotten annoyed, too, and ended up making libraries to wrap that sort of pattern for you. I like async. With it, your code might look like this:
async.each(someArray, myFunc1, myFunc2);
It offers a lot of other asynchronous building blocks, too. I'd recommend taking a look at it if you're doing lots of asynchronous stuff.
You can achieve this by using a jQuery deferred object.
var deferred = $.Deferred();
var success = function () {
// resolve the deferred with your object as the data
deferred.resolve({
result:...;
});
};
With this helper function:
function afterAll(callback,what) {
what.counter = (what.counter || 0) + 1;
return function() {
callback();
if(--what.counter == 0)
what();
};
}
your loop will look like this:
function whenAllDone() { ... }
for (... in ...) {
myFunc1(afterAll(callback,whenAllDone));
}
here afterAll creates proxy function for the callback, it also decrements the counter. And calls whenAllDone function when all callbacks are complete.
single thread is not always guaranteed. do not take it wrong.
Case 1:
For example, if we have 2 functions as follows.
var count=0;
function1(){
alert("this thread will be suspended, count:"+count);
}
function2(){
//anything
count++;
dump(count+"\n");
}
then before function1 returns, function2 will also be called, if 1 thread is guaranteed, then function2 will not be called before function1 returns. You can try this. and you will find out count is going up while you are being alerted.
Case 2: with Firefox, chrome code, before 1 function returns (no alert inside), another function can also be called.
So a mutex lock is indeed needed.
There are many, many ways to achieve this, I hope these suggestions help!
First, I would transform the callback into a promise! Here is one way to do that:
function aPromise(arg) {
return new Promise((resolve, reject) => {
aCallback(arg, (err, result) => {
if(err) reject(err);
else resolve(result);
});
})
}
Next, use reduce to process the elements of an array one by one!
const arrayOfArg = ["one", "two", "three"];
const promise = arrayOfArg.reduce(
(promise, arg) => promise.then(() => aPromise(arg)), // after the previous promise, return the result of the aPromise function as the next promise
Promise.resolve(null) // initial resolved promise
);
promise.then(() => {
// carry on
});
If you want to process all elements of an array at the same time, use map an Promise.all!
const arrayOfArg = ["one", "two", "three"];
const promise = Promise.all(arrayOfArg.map(
arg => aPromise(arg)
));
promise.then(() => {
// carry on
});
If you are able to use async / await then you could just simply do this:
const arrayOfArg = ["one", "two", "three"];
for(let arg of arrayOfArg) {
await aPromise(arg); // wow
}
// carry on
You might even use my very cool synchronize-async library like this:
const arrayOfArg = ["one", "two", "three"];
const context = {}; // can be any kind of object, this is the threadish context
for(let arg of arrayOfArg) {
synchronizeCall(aPromise, arg); // synchronize the calls in the given context
}
join(context).then(() => { // join will resolve when all calls in the context are finshed
// carry on
});
And last but not least, use the fine async library if you really don't want to use promises.
const arrayOfArg = ["one", "two", "three"];
async.each(arrayOfArg, aCallback, err => {
if(err) throw err; // handle the error!
// carry on
});
If I have a function that's passed this function:
function(work) {
work(10);
work(20);
work(30);
}
(There can be any number of work calls with any number in them.)
work performance some asynchronous activity—say, for this example, it just is a timeout. I have full control over what work does on the completion of this operation (and, in fact, its definition in general).
What's the best way of determining when all the calls to work are done?
My current method increments a counter when work is called and decrements it when it completes, and fires the all work done event when the counter is 0 (this is checked after every decrement). However, I worry that this could be a race condition of some sort. If that is not the case, do show my why and that would be a great answer.
There are a ton of ways you can write this program, but your simple technique of using a counter will work just fine.
The important thing to remember, the reason this will work, is because Javascript executes in a single thread. This is true of all browsers and node.js AFAIK.
Based on the thoughtful comments below, the solution works because the JS event loop will execute the functions in an order like:
function(work)
work(10)
counter++
Start async function
work(20)
counter++
Start async function
work(30)
counter++
Start async function
-- back out to event loop --
Async function completes
counter--
-- back out to event loop --
Async function completes
counter--
-- back out to event loop --
Async function completes
counter--
Counter is 0, so you fire your work done message
-- back out to event loop --
There's no race condition. There is the added requirement for every request made to perform a decrement when it's finished (always! including on http failure, which is easy to forget). But that can be handled in a more encapsulated way by wrapping you calls.
Untested, but this is the gist (I've implemented an object instead of a counter, so theoretically you can extend this to have more granular queries about specific requests):
var ajaxWrapper = (function() {
var id = 0, calls = {};
return {
makeRequest: function() {
$.post.apply($, arguments); // for example
calls[id] = true;
return id++;
},
finishRequest: function(id) {
delete calls[id];
},
isAllDone: function(){
var prop;
for(prop in calls) {
if(calls.hasOwnProperty(prop)) {return false;}
}
return true;
}
};
})();
Usage:
Instead of $.post("url", ... function(){ /*success*/ } ... ); We'll do
var requestId;
requestId = ajaxWrapper.makeRequest("url", ...
function(){ /*success*/ ajaxWrapper.finishRequest(requestId); } ... );
If you wanted to be even more sophisticated you could add the calls to finishRequest yourself inside the wrapper, so usage would be almost entirely transparent:
ajaxWrapper.makeRequest("url", ... function(){ /*success*/ } ... );
I have an after utility function.
var after = function _after(count, f) {
var c = 0, results = [];
return function _callback() {
switch (arguments.length) {
case 0: results.push(null); break;
case 1: results.push(arguments[0]); break;
default: results.push(Array.prototype.slice.call(arguments)); break;
}
if (++c === count) {
f.apply(this, results);
}
};
};
The following code below would just work. Because javascript is single threaded.
function doWork(work) {
work(10);
work(20);
work(30);
}
WorkHandler(doWork);
function WorkHandler(cb) {
var counter = 0,
finish;
cb(function _work(item) {
counter++;
// somethingAsync calls `finish` when it's finished
somethingAsync(item, function _cb() {
finish()
});
});
finish = after(counter, function() {
console.log('work finished');
});
};
I guess I should explain.
We pass the function that does work to the workhandler.
The work handler calls it and passes in work.
The function that does work calls work multiple times incrementing the counter
Since the function that does work is not asynchronous (very important) we can define the finish function after it has finished.
The asynchronouswork that is being done cannot finish (and call the undefined finish function) before the current synchronous block of work (the execution of the entire workhandler) has finished.
This means that after the entire workhandler has finished (and the variable finish is set) the asynchronous work jobs will start to end and call finish. Only once all of them have called finish will the callback send to after fire.
I have a Javascript object that requires 2 calls out to an external server to build its contents and do anything meaningful. The object is built such that instantiating an instance of it will automatically make these 2 calls. The 2 calls share a common callback function that operates on the returned data and then calls another method. The problem is that the next method should not be called until both methods return. Here is the code as I have implemented it currently:
foo.bar.Object = function() {
this.currentCallbacks = 0;
this.expectedCallbacks = 2;
this.function1 = function() {
// do stuff
var me = this;
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
};
this.function2 = function() {
// do stuff
var me = this;
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
};
this.commonCallback = function(resp) {
this.currentCallbacks++;
// do stuff
if (this.currentCallbacks == this.expectedCallbacks) {
// call new method
}
};
this.function1();
this.function2();
}
As you can see, I am forcing the object to continue after both calls have returned using a simple counter to validate they have both returned. This works but seems like a really poor implementation. I have only worked with Javascript for a few weeks now and am wondering if there is a better method for doing the same thing that I have yet to stumble upon.
Thanks for any and all help.
Unless you're willing to serialize the AJAX there is no other way that I can think of to do what you're proposing. That being said, I think what you have is fairly good, but you might want to clean up the structure a bit to not litter the object you're creating with initialization data.
Here is a function that might help you:
function gate(fn, number_of_calls_before_opening) {
return function() {
arguments.callee._call_count = (arguments.callee._call_count || 0) + 1;
if (arguments.callee._call_count >= number_of_calls_before_opening)
fn.apply(null, arguments);
};
}
This function is what's known as a higher-order function - a function that takes functions as arguments. This particular function returns a function that calls the passed function when it has been called number_of_calls_before_opening times. For example:
var f = gate(function(arg) { alert(arg); }, 2);
f('hello');
f('world'); // An alert will popup for this call.
You could make use of this as your callback method:
foo.bar = function() {
var callback = gate(this.method, 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
The second callback, whichever it is will ensure that method is called. But this leads to another problem: the gate function calls the passed function without any context, meaning this will refer to the global object, not the object that you are constructing. There are several ways to get around this: You can either close-over this by aliasing it to me or self. Or you can create another higher order function that does just that.
Here's what the first case would look like:
foo.bar = function() {
var me = this;
var callback = gate(function(a,b,c) { me.method(a,b,c); }, 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
In the latter case, the other higher order function would be something like the following:
function bind_context(context, fn) {
return function() {
return fn.apply(context, arguments);
};
}
This function returns a function that calls the passed function in the passed context. An example of it would be as follows:
var obj = {};
var func = function(name) { this.name = name; };
var method = bind_context(obj, func);
method('Your Name!');
alert(obj.name); // Your Name!
To put it in perspective, your code would look as follows:
foo.bar = function() {
var callback = gate(bind_context(this, this.method), 2);
sendAjax(new Request(), callback);
sendAjax(new Request(), callback);
}
In any case, once you've made these refactorings you will have cleared up the object being constructed of all its members that are only needed for initialization.
I can add that Underscore.js has a nice little helper for this:
Creates a version of the function that will only be run after first
being called count times. Useful for grouping asynchronous responses,
where you want to be sure that all the async calls have finished,
before proceeding.
_.after(count, function)
The code for _after (as-of version 1.5.0):
_.after = function(times, func) {
return function() {
if (--times < 1) {
return func.apply(this, arguments);
}
};
};
The license info (as-of version 1.5.0)
There is barely another way than to have this counter. Another option would be to use an object {} and add a key for every request and remove it if finished. This way you would know immediately which has returned. But the solution stays the same.
You can change the code a little bit. If it is like in your example that you only need to call another function inside of commonCallback (I called it otherFunction) than you don't need the commonCallback. In order to save the context you did use closures already. Instead of
foo.bar.sendRequest(new RequestObject, function(resp) {
me.commonCallback(resp);
});
you could do it this way
foo.bar.sendRequest(new RequestObject, function(resp) {
--me.expectedCallbacks || me.otherFunction(resp);
});
That's some good stuff Mr. Kyle.
To put it a bit simpler, I usually use a Start and a Done function.
-The Start function takes a list of functions that will be executed.
-The Done function gets called by the callbacks of your functions that you passed to the start method.
-Additionally, you can pass a function, or list of functions to the done method that will be executed when the last callback completes.
The declarations look like this.
var PendingRequests = 0;
function Start(Requests) {
PendingRequests = Requests.length;
for (var i = 0; i < Requests.length; i++)
Requests[i]();
};
//Called when async responses complete.
function Done(CompletedEvents) {
PendingRequests--;
if (PendingRequests == 0) {
for (var i = 0; i < CompletedEvents.length; i++)
CompletedEvents[i]();
}
}
Here's a simple example using the google maps api.
//Variables
var originAddress = "*Some address/zip code here*"; //Location A
var formattedAddress; //Formatted address of Location B
var distance; //Distance between A and B
var location; //Location B
//This is the start function above. Passing an array of two functions defined below.
Start(new Array(GetPlaceDetails, GetDistances));
//This function makes a request to get detailed information on a place.
//Then callsback with the **GetPlaceDetailsComplete** function
function GetPlaceDetails() {
var request = {
reference: location.reference //Google maps reference id
};
var PlacesService = new google.maps.places.PlacesService(Map);
PlacesService.getDetails(request, GetPlaceDetailsComplete);
}
function GetPlaceDetailsComplete(place, status) {
if (status == google.maps.places.PlacesServiceStatus.OK) {
formattedAddress = place.formatted_address;
Done(new Array(PrintDetails));
}
}
function GetDistances() {
distService = new google.maps.DistanceMatrixService();
distService.getDistanceMatrix(
{
origins: originAddress,
destinations: [location.geometry.location], //Location contains lat and lng
travelMode: google.maps.TravelMode.DRIVING,
unitSystem: google.maps.UnitSystem.IMPERIAL,
avoidHighways: false,
avoidTolls: false
}, GetDistancesComplete);
}
function GetDistancesComplete(results, status) {
if (status == google.maps.DistanceMatrixStatus.OK) {
distance = results[0].distance.text;
Done(new Array(PrintDetails));
}
}
function PrintDetails() {
alert(*Whatever you feel like printing.*);
}
So in a nutshell, what we're doing here is
-Passing an array of functions to the Start function
-The Start function calls the functions in the array and sets the number of PendingRequests
-In the callbacks for our pending requests, we call the Done function
-The Done function takes an array of functions
-The Done function decrements the PendingRequests counter
-If their are no more pending requests, we call the functions passed to the Done function
That's a simple, but practicle example of sychronizing web calls. I tried to use an example of something that's widely used, so I went with the Google maps api. I hope someone finds this useful.
Another way would be to have a sync point thanks to a timer. It is not beautiful, but it has the advantage of not having to add the call to the next function inside the callback.
Here the function execute_jobs is the entry point. it take a list of data to execute simultaneously. It first sets the number of jobs to wait to the size of the list. Then it set a timer to test for the end condition (the number falling down to 0). And finally it sends a job for each data. Each job decrease the number of awaited jobs by one.
It would look like something like that:
var g_numJobs = 0;
function async_task(data) {
//
// ... execute the task on the data ...
//
// Decrease the number of jobs left to execute.
--g_numJobs;
}
function execute_jobs(list) {
// Set the number of jobs we want to wait for.
g_numJobs = list.length;
// Set the timer (test every 50ms).
var timer = setInterval(function() {
if(g_numJobs == 0) {
clearInterval(timer);
do_next_action();
}
}, 50);
// Send the jobs.
for(var i = 0; i < list.length; ++i) {
async_task(list[i]));
}
}
To improve this code you can do a Job and JobList classes. The Job would execute a callback and decrease the number of pending jobs, while the JobList would aggregate the timer and call the callback to the next action once the jobs are finished.
I shared the same frustration. As I chained more asynchronous calls, it became a callback hell. So, I came up with my own solution. I'm sure there are similar solutions out there, but I wanted to create something very simple and easy to use. Asynq is a script that I wrote to chain asynchronous tasks. So to run f2 after f1, you can do:
asynq.run(f1, f2)
You can chain as many functions as you want. You can also specify parameters or run a series of tasks on elements in an array too. I hope this library can solve your issues or similar issues others are having.