This is my first post on stackoverflow, so please don't flame me too hard if I come across like a total nitwit or if I'm unable ot make myself perfectly clear. :-)
Here's my problem: I'm trying to write a javascript function that "ties" two functions to another by checking the first one's completion and then executing the second one.
The easy solution to this obviously would be to write a meta function that calls both functions within it's scope. However, if the first function is asynchronous (specifically an AJAX call) and the second function requires the first one's result data, that simply won't work.
My idea for a solution was to give the first function a "flag", i.e. making it create a public property "this.trigger" (initialized as "0", set to "1" upon completion) once it is called; doing that should make it possible for another function to check the flag for its value ([0,1]). If the condition is met ("trigger == 1") the second function should get called.
The following is an abstract example code that I have used for testing:
<script type="text/javascript" >
/**/function cllFnc(tgt) { //!! first function
this.trigger = 0 ;
var trigger = this.trigger ;
var _tgt = document.getElementById(tgt) ; //!! changes the color of the target div to signalize the function's execution
_tgt.style.background = '#66f' ;
alert('Calling! ...') ;
setTimeout(function() { //!! in place of an AJAX call, duration 5000ms
trigger = 1 ;
},5000) ;
}
/**/function rcvFnc(tgt) { //!! second function that should get called upon the first function's completion
var _tgt = document.getElementById(tgt) ; //!! changes color of the target div to signalize the function's execution
_tgt.style.background = '#f63' ;
alert('... Someone picked up!') ;
}
/**/function callCheck(obj) {
//alert(obj.trigger ) ; //!! correctly returns initial "0"
if(obj.trigger == 1) { //!! here's the problem: trigger never receives change from function on success and thus function two never fires
alert('trigger is one') ;
return true ;
} else if(obj.trigger == 0) {
return false ;
}
}
/**/function tieExc(fncA,fncB,prms) {
if(fncA == 'cllFnc') {
var objA = new cllFnc(prms) ;
alert(typeof objA + '\n' + objA.trigger) ; //!! returns expected values "object" and "0"
}
//room for more case definitions
var myItv = window.setInterval(function() {
document.getElementById(prms).innerHTML = new Date() ; //!! displays date in target div to signalize the interval increments
var myCallCheck = new callCheck(objA) ;
if( myCallCheck == true ) {
if(fncB == 'rcvFnc') {
var objB = new rcvFnc(prms) ;
}
//room for more case definitions
window.clearInterval(myItv) ;
} else if( myCallCheck == false ) {
return ;
}
},500) ;
}
</script>
The HTML part for testing:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd >
<html>
<head>
<script type="text/javascript" >
<!-- see above -->
</script>
<title>
Test page
</title>
</head>
<body>
<!-- !! testing area -->
<div id='target' style='float:left ; height:6em ; width:8em ; padding:0.1em 0 0 0; font-size:5em ; text-align:center ; font-weight:bold ; color:#eee ; background:#fff;border:0.1em solid #555 ; -webkit-border-radius:0.5em ;' >
Test Div
</div>
<div style="float:left;" >
<input type="button" value="tie calls" onmousedown="tieExc('cllFnc','rcvFnc','target') ;" />
</div>
<body>
</html>
I'm pretty sure that this is some issue with javascript scope as I have checked whether the trigger gets set to "1" correctly and it does. Very likely the "checkCall()" function does not receive the updated object but instead only checks its old instance which obviously never flags completion by setting "this.trigger" to "1". If so I don't know how to address that issue.
Anyway, hope someone has an idea or experience with this particular kind of problem.
Thanks for reading!
FK
You can take advantage of a feature of JS called closure. Combine that with a very common JS pattern called "continuation passing style" and you have your solution. (Neither of these things are original to JS, but are heavily used in JS).
// a function
function foo(some_input_for_foo, callback)
{
// do some stuff to get results
callback(results); // call our callback when finished
}
// same again
function bar(some_input_for_bar, callback)
{
// do some stuff to get results
callback(results); // call our callback when finished
}
The "continuation passing style" refers to the callback. Instead of returning a value, each function calls a callback (the continuation) and gives it the results.
You can then tie the two together easily:
foo(input1, function(results1) {
bar(results1, function(results2) {
alert(results2);
});
});
The nested anonymous functions can "see" variables from the scope they live in. So there's no need to use special properties to pass information around.
Update
To clarify, in your question's code snippet, it's clear that you are thinking roughly like this:
I have a long-running asynchronous
operation, so I need to know when it
finishes in order to start the next
operation. So I need to make that
state visible as a property. Then
elsewhere I can run in a loop,
repeatedly examining that property to
see when it changes to the "completed"
state, so I know when to continue.
(And then as a complicating factor, the loop has to use setInterval to start running and clearInterval to quit, to allow other JS code to run - but it's basically a "polling loop" nevertheless).
You do not need to do that!
Instead of making your first function set a property on completion, make it call a function.
To make this absolutely clear, let's refactor your original code:
function cllFnc(tgt) { //!! first function
this.trigger = 0 ;
var trigger = this.trigger ;
var _tgt = document.getElementById(tgt) ; //!! changes the color...
_tgt.style.background = '#66f' ;
alert('Calling! ...') ;
setTimeout(function() { //!! in place of an AJAX call, duration 5000ms
trigger = 1 ;
},5000) ;
}
[Update 2: By the way, there's a bug there. You copy the current value of the trigger property into a new local variable called trigger. Then at the end you assign 1 to that local variable. No one else is going to be able to see that. Local variables are private to a function. But you don't need to do any of this anyway, so keep reading...]
All we have to do is tell that function what to call when it's done, and get rid of the property-setting:
function cllFnc(tgt, finishedFunction) { //!! first function
var _tgt = document.getElementById(tgt) ; //!! changes the color...
_tgt.style.background = '#66f' ;
alert('Calling! ...') ;
setTimeout(function() { //!! in place of an AJAX call, duration 5000ms
finishedFunction(); // <-------- call function instead of set property
},5000) ;
}
There's now no need for your "call-check" or your special tieExc helper. You can easily tie two functions together with very little code.
var mySpan = "#myspan";
cllFnc(mySpan, function() { rcvFnc(mySpan); });
Another advantage of this is that we can pass different parameters to the second function. With your approach, the same parameters are passed to both.
For example, the first function might do a couple of calls to an AJAX service (using jQuery for brevity):
function getCustomerBillAmount(name, callback) {
$.get("/ajax/getCustomerIdByName/" + name, function(id) {
$.get("/ajax/getCustomerBillAmountById/" + id), callback);
});
}
Here, callback accepts the customer bill amount, and the AJAX get call passes the received value to the function we pass it, so the callback is already compatible and so can directly act as the callback for the second AJAX call. So this is itself an example of tying two asynchronous calls together in sequence and wrapping them in what appears (from the outside) to be a single asynchronous function.
Then we can chain this with another operation:
function displayBillAmount(amount) {
$("#billAmount").text(amount);
}
getCustomerBillAmount("Simpson, Homer J.", displayBillAmount);
Or we could (again) have used an anonymous function:
getCustomerBillAmount("Simpson, Homer J.", function(amount) {
$("#billAmount").text(amount);
});
So by chaining function calls like this, each step can pass information forward to the next step as soon as it is available.
By making functions execute a callback when they're done, you are freed from any limitations to how each functions works internally. It can do AJAX calls, timers, whatever. As long as the "continuation" callback is passed forward, there can be any number of layers of asynchronous work.
Basically, in an asynchronous system, if you ever find yourself writing a loop to check a variable and find out if it has changed state, then something has gone wrong somewhere. Instead there should be a way to supply a function that will be called when the state changes.
Update 3
I see elsewhere in comments you mention that the actual problem is caching results, so all my work explaining this was a waste of time. This is the kind of thing you should put in the question.
Update 4
More recently I've written a short blog post on the subject of caching asynchronous call results in JavaScript.
(end of update 4)
Another way to share results is to provide a way for one callback to "broadcast" or "publish" to several subscribers:
function pubsub() {
var subscribers = [];
return {
subscribe: function(s) {
subscribers.push(s);
},
publish: function(arg1, arg2, arg3, arg4) {
for (var n = 0; n < subscribers.length; n++) {
subscribers[n](arg1, arg2, arg3, arg4);
}
}
};
}
So:
finished = pubsub();
// subscribe as many times as you want:
finished.subscribe(function(msg) {
alert(msg);
});
finished.subscribe(function(msg) {
window.title = msg;
});
finished.subscribe(function(msg) {
sendMail("admin#mysite.com", "finished", msg);
});
Then let some slow operation publish its results:
lookupTaxRecords("Homer J. Simpson", finished.publish);
When that one call finishes, it will now call all three subscribers.
The definitive answer to this "call me when you're ready" problem is a callback. A callback is basically a function that you assign to an object property (like "onload"). When object state changes, the function is called. For example, this function makes an ajax request to the given url and screams when it's complete:
function ajax(url) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function (aEvt) {
if(req.readyState == 4)
alert("Ready!")
}
req.send(null);
}
Of course, this is not flexible enough, because we presumably want different actions for different ajax calls. Fortunately, javascript is a functional language, so we can simply pass the required action as a parameter:
function ajax(url, action) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function (aEvt) {
if(req.readyState == 4)
action(req.responseText);
}
req.send(null);
}
This second function can be used like this:
ajax("http://...", function(text) {
do something with ajax response
});
As per comments, here an example how to use ajax within an object
function someObj()
{
this.someVar = 1234;
this.ajaxCall = function(url) {
var req = new XMLHttpRequest();
req.open('GET', url, true);
var me = this; // <-- "close" this
req.onreadystatechange = function () {
if(req.readyState == 4) {
// save data...
me.data = req.responseText;
// ...and/or process it right away
me.process(req.responseText);
}
}
req.send(null);
}
this.process = function(data) {
alert(this.someVar); // we didn't lost the context
alert(data); // and we've got data!
}
}
o = new someObj;
o.ajaxCall("http://....");
The idea is to "close" (aliased) "this" in the event handler, so that it can be passed further.
Welcome to SO! Btw, You come across as a total nitwit and your question is totally unclear :)
This is building upon #Daniel's answer of using continuations. It is a simple function that chains multiple methods together. Much like how the pipe | works in unix. It takes a set of functions as its arguments which are to be executed sequentially. The return value of each function call is passed on to the next function as a parameter.
function Chain() {
var functions = arguments;
return function(seed) {
var result = seed;
for(var i = 0; i < functions.length; i++) {
result = functions[i](result);
}
return result;
}
}
To use it, create an object from Chained passing all functions as parameters. An example you can test on fiddle would be:
var chained = new Chain(
function(a) { return a + " wo"; },
function(a) { return a + "r"; },
function(a) { return a + "ld!"; }
);
alert(chained('hello')); // hello world!
To use it with an AJAX request, pass the chained function as the success callback to the XMLHttpRequest.
var callback = new Chain(
function(response) { /* do something with ajax response */ },
function(data) { /* do something with filtered ajax data */ }
);
var req = new XMLHttpRequest();
req.open('GET', url, true);
req.onreadystatechange = function (aEvt) {
if(req.readyState == 4)
callback(req.responseText);
}
req.send(null);
The important thing is that each function depends on the output of the previous function, so you must return some value at each stage.
This is just a suggestion - giving the responsibility of checking whether data is available locally or an HTTP request must be made is going to increase the complexity of the system. Instead, you could have an opaque request manager, much like the metaFunction you have, and let it decide if the data is to be served locally or remotely.
Here is a sample Request object that handles this situation without any other objects or functions knowing where the data was served from:
var Request = {
cache: {},
get: function(url, callback) {
// serve from cache, if available
if(this.cache[url]) {
console.log('Cache');
callback(this.cache[url]);
return;
}
// make http request
var request = new XMLHttpRequest();
request.open('GET', url, true);
var self = this;
request.onreadystatechange = function(event) {
if(request.readyState == 4) {
self.cache[url] = request.responseText;
console.log('HTTP');
callback(request.responseText);
}
};
request.send(null);
}
};
To use it, you would make a call to Request.get(..), and it returns cached data if available or makes an AJAX call otherwise. A third parameter could be passed to control how long the data should be cached for, if you're looking for granular control over caching.
Request.get('<url>', function(response) { .. }); // HTTP
// assuming the first call has returned by now
Request.get('<url>', function(response) { .. }); // Cache
Request.get('<url>', function(response) { .. }); // Cache
I've worked it out and it seems to work perfectly well now. I will post my code later after I have sorted it out. In the meantime, thanks a lot for you assistance!
Update
Tried the code in Webkit (Safari, Chrome), Mozilla and Opera. Seems to work just fine. Looking forward to any replies.
Update 2
I changed the tieExc() method to integrate Anurag's chained function call syntax. Now you can call as many functions as you want upon completion check by passing them as arguments.
If you are not inclined to read the code, try it: http://jsfiddle.net/UMuj3/ (btw, JSFiddle is a really neat site!).
JS-Code:
/**/function meta() {
var myMeta = this ;
/** **/this.cllFnc = function(tgt,lgt) { //!! first function
this.trigger = 0 ; //!! status flag, initially zero
var that = this ; //!! required to access parent scope from inside nested function
var _tgt = document.getElementById(tgt) ; //!! changes the color of the target div to signalize the function's execution
_tgt.style.background = '#66f' ;
alert('Calling! ...') ;
setTimeout(function() { //!! simulates longer AJAX call, duration 5000ms
that.trigger = 1 ; //!! status flag, one upon completion
},5000) ;
} ;
/** **/this.rcvFnc = function(tgt) { //!! second function that should get called upon the first function's completion
var _tgt = document.getElementById(tgt) ; //!! changes color of the target div to signalize the function's execution
_tgt.style.background = '#f63' ;
alert('... Someone picked up!') ;
} ;
/** **/this.callCheck = function(obj) {
return (obj.trigger == 1) ? true
: false
;
} ;
/** **/this.tieExc = function() {
var functions = arguments ;
var myItv = window.setInterval(function() {
document.getElementById('target').innerHTML = new Date() ; //!! displays date in target div to signalize the interval increments
var myCallCheck = myMeta.callCheck(functions[0]) ; //!! checks property "trigger"
if(myCallCheck == true) {
clearInterval(myItv) ;
for(var n=1; n < functions.length; n++) {
functions[n].call() ;
}
} else if(myCallCheck == false) {
return ;
}
},100) ;
} ;
}
HTML:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/strict.dtd >
<html>
<head>
<script type='text/javascript' >
<!-- see above -->
</script>
<title>
Javascript Phased Execution Test Page
</title>
</head>
<body>
<div id='target' style='float:left ; height:7.5em ; width:10em ; padding:0.5em 0 0 0; font-size:4em ; text-align:center ; font-weight:bold ; color:#eee ; background:#fff;border:0.1em solid #555 ; -webkit-border-radius:0.5em ;' >
Test Div
</div>
<div style="float:left;" >
<input type="button" value="tieCalls()" onmousedown="var myMeta = new meta() ; var myCll = new myMeta.cllFnc('target') ; new myMeta.tieExc(myCll, function() { myMeta.rcvFnc('target') ; }, function() { alert('this is fun stuff!') ; } ) ;" /><br />
</div>
<body>
</html>
A very simple solution would be to make your first ajax call synchronous. It's one of the optional parameters.
Related
I have written a basic job runner in javascript (utilizing some JQuery too, but that's another story for another day) and I came across this queer Issue:
The method I run to wait for all jobs to complete:
$.getAllProducts = function(callback){
$.getProductDetails('|ALL|', function(allProductsResult){ //intentionally
var objAllProducts = JSON.parse(JSON.parse(allProductsResult));
var objProductsBuiltUp = {};
var productLength = objAllProducts.length;
$.totalJobs(productLength);
var processed = 0;
$.completedJobs(processed);
$.each(objAllProducts, function(i,v){
$.getProductDetails(objAllProducts[i].ProductCode, function(result){
$.mergeShallow(objProductsBuiltUp, JSON.parse(JSON.parse(result)));
processed++;
$.completedJobs(processed);
});
});
$.wait(0, false, function(isDone){ //allow at least 50ms wait time, otherwise this confuses javascript into thinking there are no callbacks
if (isDone){
callback(objProductsBuiltUp.ProductComponents);
}
});
});
}
The handlers for the job
$.checkProgress = function() {
return $.jobs === $.completed;
}
$.totalJobs = function(total) {
$.jobs = total;
}
$.completedJobs = function(completed) {
$.completed = completed;
}
$.wait = function(timeout, debug, callback) {
setTimeout(function() {
if (debug) {
console.log($.completed + " / " + $.jobs + " = " + ($.completed / $.jobs * 100) + "%");
}
if ($.checkProgress() == false) {
$.wait(timeout, debug);
}
callback($.checkProgress()); // <-- complaining one
}, timeout);
}
This is the key-point code for my little job runner, other methods will call $.totalJobs() to set the amount of jobs that need to be performed (normally based on amount different calls need to be made to an API In my scenario), and $.completedJobs() - which is called when the payloads are returned in the API handler's callbacks
The issue is, when I set my "Waiter" to 50ms, I don't get any errors whatsoever, and the method performs as per expected.
When I set it to low values like 5ms, 1ms, 0ms, It tells me:
"xxxxx.helpers.js:48 Uncaught TypeError: callback is not a function"
Anyone have a wild-flung theory why this would occur? it is, afterall, only a glorified setTimeout.
(P.S. In response to why I use JQuery global methods and variables to store info is to make using Meteor easier on myself knowing it's loaded into 1 place - this is the platform I am developing on at the moment.)
EDIT was better for me to add the whole method where the callback is run
It looks like you're not passing a callback here:
$.wait = function(timeout, debug, callback) {
//code here
if ($.checkProgress() == false) {
$.wait(timeout, debug); // === $.wait(timeout, debug, undefined);
}
callback($.checkProgress()); // <-- complaining one
}, timeout);
so if $.checkProgress() is false, you're calling $.wait recursively only callback is undefined...
At first glance, I think what you wanted to write there was:
$.wait(timeout, debug, callback); // pass callback argument to inner call
But then obviously, you wouldn't want to invoke the callback multiple times:
$.wait = function(timeout, debug, callback) {
//code here
if ($.checkProgress() == false) {
$.wait(timeout, debug, callback);
} else {
callback($.checkProgress());
}
}, timeout);
The reason why the line you marked as "the complaining one" is in fact complaining is because it's the recursive call. $.checkProgress evaluated to false, the $.wait function is invoked (this time, callback is undefined), and that continues until $.checkProgress() === false evaluates to false. Then, callback (which is undefined) will get invoked in the inner call.
This issue started appearing when the interval was reduced down. That makes sense, because you're only recursively calling $.wait if the jobs hadn't been completed. The higher the timeout/interval, the greater the chance the jobs were completed the first time around.
By reducing the interval, you arrived at a point where $.wait got invoked before the jobs had finished, and you entered the $.checkProgress() === false branch, calling $.wait without passing the callback (essentially losing a reference to it).
By the time the jobs had completed, you were trying to invoke callbackm which was set to undefined.
In
if ($.checkProgress() == false) {
$.wait(timeout, debug);
}
you're not passing through the callback parameter, so in the "recursive" call it will be undefined and you're getting the exception you posted. Make it
if ($.checkProgress() == false) {
$.wait(timeout, debug, callback);
// ^^^^^^^^
}
This is for a game I'm writing in JavaScript.
I've got an array of objects (pieces) each with multiple methods (Method_1 .. Method_N). Meanwhile I have some other function (gameAI) that determines in what order the objects should call what methods.
//Array of objects definition
function gamePiece() {
this.CallBack = null;
this.Method_1 = function...
this.Method_2 = function...
this.Method_3 = function...
this.Method_N = function() {
//do things...
if( this.CallBack != null ) {
if( this.CallBack != null) {
// Question is here
this.CallBack(); // <-- I do not want this.CallBack, I want that.CallBack()
}
}
}
}
var pieces = new Array();
for(var i=0; i<10; i++) {
pieces.push = new gamePiece();
}
function gameAI() {
pieces[4].CallBack = pieces[3].Method_1;
pieces[3].CallBack = pieces[2].Method_2;
pieces[2].CallBack = pieces[1].Method_1;
pieces[4].Method_2();
}
gameAI();
So in this example:
First piece 4 calls method_2
After this completes piece 3 calls method_1
After this completes piece 2 calls method_2
After this completes piece 1 calls method_1
Since piece 1 has no callback defined nothing more occurs
The behavior I am seeing is that when piece 4 does this.Callback() it calls it from the context of itself not from the context of piece 3. So it would seem Callback stores the function to call but not the caller.
To address this I changed the code to the following:
....
snip
....
pieces[4].CallBack = ({
Sender: pieces[3]
,Method: pieces[3].Method_1
});
....
snip
....
if( this.CallBack != null ) {
this.CallBack.Sender.????
? perhaps some sort of ?
this.CallBack.Method.call(this.CallBack.Sender)
}
Any ideas?
As raina77ow mentioned in the comment, your logic seems very complicated. Anyway, a solution to the problem you're having is to bind the method you want:
pieces[4].CallBack = pieces[3].Method_1.bind(pieces[3]);
That will ensure that when the callback is executed, the context it runs in will be pieces[3].
Apparently I do not have enough points to comment, so apologies to StackOverflow maintenance persons.
#raina77ow
Game programming is often very complicated, the example I gave here was stripped down to just communicate the question. Each method has it's own asynchronous methods with it's own callbacks.
something like
var methodsToCall = new Array();
methodsToCall.push(...
methodsToCall.push(...
methodsToCall.push(...
...
for(var i in methodsToCall) {
i();
}
would not work. --- Was that what you had in mind?
when I try the following:
if(myFunction() == "3") {
alert('its three!');
}
my function returns undefined, when I know that it is actually returning 3.
Is this because javascript evaluates the if statement before the function can return a value?
if so how can I do something like this:
var result = myFunction();
// Wait until result is there
if(result == "3") {
alert("its three!");
}
To troubleshoot your problem, try calling the "alert" function with the return value of your function as the argument:
var result = myFunction();
// For debugging
alert(result);
if(result == "3") {
alert("its three!");
}
If the contents of the alert box are "undefined", your function is not in fact returning a value, and just assigning that non-existent result to a variable is not going to help. Check to make sure myFunction looks like:
function myFunction() {
// Perform calculations
var n = 10 - 7;
// Return result
return n;
}
The JavaScript interpreter requires neither functions to return values nor return statements to be used. That means you should double-check that there is in fact a return statement. If you are assigning the result to a variable first, that can be one way you could miss that return statement.
If you are dealing with Ajax requests, jQuery "modal dialogs," or similar constructs, it is not even possible for a function to return a value in this way (when the button is clicked or the request completes, that is). To fix the problem, your function needs to accept a callback function as a parameter. Then when done, it must call that function, passing the "returned value" as an argument:
function myFunction(callback) {
// Start an AJAX request
var x = new XMLHttpRequest();
x.open('GET', '/domath.php?expression=10-7', true);
x.onreadystatechange = function() {
if(x.readyState == 4 && x.status == 200) {
// Call callback function, "returning" a value asynchronously
callback(x.responseText);
}
};
x.send();
}
And then your main code would be:
myFunction(function(result) {
// For debugging
alert(result);
if(result == "3") {
alert("its three!");
}
});
What it sounds like is that your javascript code is calling the function before the function or the elements it accesses (ie something in the DOM) have been fully loaded, which is why the function call is returning undefined instead of '3'.
The way to prevent this is to defer calling the function until the DOM has finished loading.
This is typically done by having the function call in your document.onload() method, which only gets run when the page has finished loading, or by using jQuery's $.ready() method, which again waits until the page is ready before being run.
Hope that helps.
I am a little unclear as to what you are doing from the description. Try this to see if its what you wanted:
function MyFunction(){
var result
// Do something here
//dummy:
result = 3;
return result;
}
var Testing = MyFunction();
alert(Testing);
if (Testing == 3) {
alert("Holy Cow! Its 3!");
}
I think this issue goes beyond typical variable scope and closure stuff, or maybe I'm an idiot. Here goes anyway...
I'm creating a bunch of objects on the fly in a jQuery plugin. The object look something like this
function WedgePath(canvas){
this.targetCanvas = canvas;
this.label;
this.logLabel = function(){ console.log(this.label) }
}
the jQuery plugin looks something like this
(function($) {
$.fn.myPlugin = function() {
return $(this).each(function() {
// Create Wedge Objects
for(var i = 1; i <= 30; i++){
var newWedge = new WedgePath(canvas);
newWedge.label = "my_wedge_"+i;
globalFunction(i, newWedge]);
}
});
}
})(jQuery);
So... the plugin creates a bunch of wedgeObjects, then calls 'globalFunction' for each one, passing in the latest WedgePath instance. Global function looks like this.
function globalFunction(indicator_id, pWedge){
var targetWedge = pWedge;
targetWedge.logLabel();
}
What happens next is that the console logs each wedges label correctly. However, I need a bit more complexity inside globalFunction. So it actually looks like this...
function globalFunction(indicator_id, pWedge){
var targetWedge = pWedge;
someSql = "SELECT * FROM myTable WHERE id = ?";
dbInterface.executeSql(someSql, [indicator_id], function(transaction, result){
targetWedge.logLabel();
})
}
There's a lot going on here so i'll explain. I'm using client side database storage (WebSQL i call it). 'dbInterface' an instance of a simple javascript object I created which handles the basics of interacting with a client side database [shown at the end of this question]. the executeSql method takes up to 4 arguments
The SQL String
an optional arguments array
an optional onSuccess handler
an optional onError handler (not used in this example)
What I need to happen is: When the WebSQL query has completed, it takes some of that data and manipulates some attribute of a particular wedge. But, when I call 'logLabel' on an instance of WedgePath inside the onSuccess handler, I get the label of the very last instance of WedgePath that was created way back in the plugin code.
Now I suspect that the problem lies in the var newWedge = new WedgePath(canvas); line. So I tried pushing each newWedge into an array, which I thought would prevent that line from replacing or overwriting the WedgePath instance at every iteration...
wedgeArray = [];
// Inside the plugin...
for(var i = 1; i <= 30; i++){
var newWedge = new WedgePath(canvas);
newWedge.label = "my_wedge_"+i;
wedgeArray.push(newWedge);
}
for(var i = 0; i < wedgeArray.length; i++){
wedgeArray[i].logLabel()
}
But again, I get the last instance of WedgePath to be created.
This is driving me nuts. I apologise for the length of the question but I wanted to be as clear as possible.
END
==============================================================
Also, here's the code for dbInterface object should it be relevant.
function DatabaseInterface(db){
var DB = db;
this.sql = function(sql, arr, pSuccessHandler, pErrorHandler){
successHandler = (pSuccessHandler) ? pSuccessHandler : this.defaultSuccessHandler;
errorHandler = (pErrorHandler) ? pErrorHandler : this.defaultErrorHandler;
DB.transaction(function(tx){
if(!arr || arr.length == 0){
tx.executeSql(sql, [], successHandler, errorHandler);
}else{
tx.executeSql(sql,arr, successHandler, errorHandler)
}
});
}
// ----------------------------------------------------------------
// A Default Error Handler
// ----------------------------------------------------------------
this.defaultErrorHandler = function(transaction, error){
// error.message is a human-readable string.
// error.code is a numeric error code
console.log('WebSQL Error: '+error.message+' (Code '+error.code+')');
// Handle errors here
var we_think_this_error_is_fatal = true;
if (we_think_this_error_is_fatal) return true;
return false;
}
// ----------------------------------------------------------------
// A Default Success Handler
// This doesn't do anything except log a success message
// ----------------------------------------------------------------
this.defaultSuccessHandler = function(transaction, results)
{
console.log("WebSQL Success. Default success handler. No action taken.");
}
}
I would guess that this is due to that the client side database storage runs asynchronous as an AJAX call would. This means that it doesn't stops the call chain in order to wait for a result from the invoked method.
As a result the javascript engine completes the for-loop before running the globalFunction.
To work around this you could perform the db query inside a closure.
function getDataForIndicatorAndRegion(indicator_id, region_id, pWedge){
return function (targetWedge) {
someSql = "SELECT dataRows.status FROM dataRows WHERE indicator_id = ? AND region_id = ?";
dbInterface.sql(someSql, [indicator_id, region_id], function(transaction, result) {
targetWedge.changeColor(randomHex());
});
}(pWedge);
}
This way you preserve pWedge for each execution. Since the second method is invoking it self and send what pWedge is right now as an argument.
EDIT: Updated the code from comments. And made a change to it. The callback function maybe shouldn't be self invoked. If it invoke it self the result of the function is passed as a argument. Also if it doesn't work, try passing the other arguments.
i suspect your problem is the modifed closure going on inside globalFunction:
function(transaction, result){
targetWedge.logLabel();
})
read this
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.