How to make my JS callback work right? - javascript

I have the following problem:
I'm trying to implement a Callback in JavaScript. Now I just made it with a global variable which holds my callbacl function. Here is the example:
_callbackFkt = null;
requestCompleted = function(oControlEvent) {
console.log("Callback: " + _callbackFkt.toString());
};
myLibRequest = function(callback) {
// some code, which is attached to the requestComplete event when ready
_callbackFkt = callback;
};
Now I try to call the functions which use the callback:
myLibRequest(function () {
// callback function 1
});
myLibRequest(function () {
// callback function 2
});
myLibRequest(function () {
// callback function 3
});
the result in the console is:
Callback: function () {
// callback function 3
}
How can I define the callback to be bound to one function call and not global available? I want the result:
Callback: function () {
// callback function 1
}
Callback: function () {
// callback function 2
}
Callback: function () {
// callback function 3
}

There are several ways to do what you are trying to do, but your basic problem is that you want a list of event handlers, but you are only assigning a single value.
To modify what you are currently doing:
_callbackFkts = [];
myLibRequest = function(callback) {
// some code, which is attached to the requestComplete event when ready
_callbackFkts.push(callback);
};
Then, when you want to execute the callbacks:
_callbackFkts.forEach(function(callbackFkt) {
callbackFkt();
});
But, this global mechanism is a bit messy. You might consider some encapsulation (untested, but you get the idea):
function Events() {
this.callbacks = [];
}
Events.protototype.bind = function(callback) {
this.callbacks.push(callback);
};
Events.prototype.executeAll = function(params) {
this.callbacks.forEach(function(callback) {
callback.apply(this, params);
}
}
Then you can use it like this:
var events = new Events();
events.bind(function() {
//callback function 1
});
events.bind(function() {
//callback function 2
});
events.bind(function() {
//callback function 3
});
events.executeAll('with', 'parameters');
Finally, you might just use an off-the-shelf event library. There are lots. One quick google search finds this.

Having a global as the callback will only work if myLibRequest() contains only synchronous code (which I assume it doesn't).
Remove the global, and use the callback that is passed in as an argument.
Assuming you have some async call in there, and you call requestCompleted when it's done. Add an argument so requestCompleted receives the callback, instead of referenceing the global.
requestCompleted = function(oControlEvent, callback) {
console.log("Callback: " + callback.toString());
};
myLibRequest = function(callback) {
myAsyncFunction(function(){
// async complete
requestCompleted('event', callback);
});
};

Related

javascript callback, how does it work?

I'm trying to make a callback function, and i can't find a solution to pass an argument in this callback ..
var chrome = {
enterprise: {
deviceAttributes: {
getDirectoryDeviceId: function() {
return "test";
}
}
}
};
chrome.enterprise.deviceAttributes.getDirectoryDeviceId(function(deviceid) {
alert(deviceid);
});
How can the deviceid in the callback function be "xxx" ?
Many thanks
You are passing a function into getDirectoryDeviceId when you call it...but not calling that function or accepting it as an argument
Try:
var chrome = {
enterprise: {
deviceAttributes: {
getDirectoryDeviceId: function(callback) {
// ^^ function you already pass in when you call method
// call the callback you pass in below
callback("test");
}
}
}
};
// nothing changed here
chrome.enterprise.deviceAttributes.getDirectoryDeviceId(function(deviceid) {
alert(deviceid);
});
First, chrome.enterprise.deviceAttributes.getDirectoryDeviceId is being assigned a function that takes no arguments, so you'd need to set one up there.
Then, when you call getDirectoryDeviceId the function that you pass can be executed by appending () to the argument.
Lastly, if you want to be able to pass an argument that the callback will use, you need to set up a second argument in the function.
var chrome = {
enterprise: {
deviceAttributes: {
getDirectoryDeviceId : function(callback, data){
callback(data);
}
}
}
};
chrome.enterprise.deviceAttributes.getDirectoryDeviceId(function (deviceid){
alert(deviceid);
}, "TEST!"); // <-- Second argument is the data that will be used by the callback

Using callbacks in window.onload

I think there is an easy solution for this, but for some reason I am not getting the expected results. My functions look like this:
var functionA = function(callback) {
loadData(fromURL1); // takes some time
loadData(fromURL2); // takes some time
callback(); // Should be called AFTER the loadData() functions are finished
}
var myCallBackFunction = function() {
// this function is called AFTER functionA() is finished
alert("All my loaded data from URL1 and URL2");
}
window.onload = function() {
functionA(myCallBackFunction);
}
Unfortunately, the callback() function above doesn't wait for loadData() to finish, and then just calls the alert with empty data.
I read a lot of online examples, but I think I am still missing something obvious.
If the loadData()s are async operations, you can do two things:
Using $.ajaxComplete():
var functionA = function(callback) {
loadData(fromURL1); // takes some time
loadData(fromURL2); // takes some time
$.ajaxComplete(function () {
callback(); // Should be called AFTER the loadData() functions are finished
});
}
Or chaining the functions:
var functionA = function(callback) {
loadData(fromURL1, function () {
loadData(fromURL2, function () {
callback(); // Should be called AFTER the loadData() functions are finished
}); // takes some time
}); // takes some time
}

js execute function after object is defined

I need for a function to be executable only after an object is defined, I'm currently working in a fascade pattern and one method is dependent on another method. in this case 'addNewLayer' fails because 'setFullMap' hasn't finished executing. is there a solution? I'm using jquery and vanilla js so most any solution would be helpful at this point:
var jen = (function(){
function setFullMap(mapID){
jen.map = new Map(mapID);
}
function setLayer(opt){
//execute code here after jen.map is defined
}
return{
samp: function(id, opt){
setFullMap(id);
addNewLayer(opt);
}
};
})();
Thanks
solution:
var jen = (function(){
function setFullMap(mapID, callback) {
jen.map = new Map(mapID);
if(jen.map){
callback();
}
}
return {
samp: function(id, opt){
setFullMap(id, function(){
addNewLayer(opt);
}.bind(this));
}
};
})();
You will have to pass a callback function to setFullMap, and execute it once the function has completed (at the very end, before the closing }).
var jen = (function(){
function setFullMap(mapID, callback){
jen.map = new Map(mapID);
callback();
}
function setLayer(opt){
//execute code here after jen.map is defined
}
return{
samp: function(id, opt){
setFullMap(id, function() {
addNewLayer(opt);
}.bind(this));
}
};
})();
Do not forget using .bind(this) - it is very important in order to keep the original this in your callback function.
Edit:
Actually that would not work work if the Map constructor is a-synchronous. If you do not have access to the constructor and/or you cannot pass it a callback, then presumably the only (and sad) option would be to use a setTimeout or (easier) setInterval, continuously checking at defined intervals if the operation has been completed, and then fire the callback.
You could use a callback parameter:
function setFullmap(mapId,callback) {
jen.map = new Map(mapId);
callback();
}
....
samp: function(id, opt){
setFullMap(id,function() {
addNewLayer(opt);
});
}
When u dont have a way to manipulate the Map Object then u need to use a loop:
var loop=self.setInterval(function(){
if(jen.map) {
//execute code here after jen.map is defined
console.log(typeof jen.map);
window.clearInterval(loop);
}
},50);
Check jsfiddle:
http://jsfiddle.net/9yv5t/1/
I have checked the docs and it seems that there are various events you could listen to.
For example:
var m = new Map(...);
m.on('load', function () {
//execute code when the first layer is ready
});
var l = new Layer(...);
l.on('load', function () {
//execute code when the layer has been initialized
});
It's also carefully stated for the Layer.load event:
fires after layer properties for the layer are successfully populated.
This event must be successful before the layer can be added to the
map.

Collect data to object with four async calls and handle the object onready

I have a handler (callback), an object to handle and four functions, which collect the data to object. In my case I wish to asynchronously call four data retrievers and when execution of all four is complete, handle the resulting object (something similar to the following):
var data = {};
function handle (jsObj) {}
// data retrieving
function getColorData () {}
function getSizeData () {}
function getWeightData () {}
function getExtraData () {}
data.color = getColorData();
data.size = getSizeData();
data.weight = getWeightData();
data.extra = getExtraData();
handle( data );
Of course, this code will not work properly. And if I chain data retrieving functions, they will be called one after another, right?
All four functions should be called asynchronously, cause they are being executed for too long to call them one by one.
Updated:
Thanks to everybody for your suggestions! I prefered $.Deferred(), but I found it slightly difficult to make it work the way I need. What I need is to asynchronously make a view, which requires four kinds of data (extraData, colorData, sizeData & weightData) and I have three objects: App, Utils & Tools.
Just a small description: view is created by calling App.getStuff passed App.handleStuff as a callback. Callback in the body of App.getStuff is called only $.when(App.getExtraData(), App.getColorData(), App.getSizeData(), App.getWeightData()). Before that Utils.asyncRequest passed Tools.parseResponse as a callback is called.
So, now the question is should I create four deferred objects inside each App.get*Data() and also return deferred.promise() from each of them?
And should I deferred.resolve() in the last function in my order (Tools.parseResponse for App.getExtraData in my example)?
var view,
App,
Utils = {},
Tools = {};
// Utils
Utils.asyncRequest = function (path, callback) {
var data,
parseResponse = callback;
// do something with 'data'
parseResponse( data );
};
// Tools
Tools.parseResponse = function (data) {
var output = {};
// do something to make 'output' from 'data'
/* So, should the deferred.resolve() be done here? */
deferred.resolve(output);
/// OR deferred.resolve();
/// OR return output;
};
// App
App = {
// Only one method really works in my example
getExtraData : function () {
var deferred = new jQuery.Deferred();
Utils.asyncRequest("/dir/data.txt", Tools.parseResponse);
return deferred.promise();
},
// Others do nothing
getColorData : function () { /* ... */ },
getSizeData : function () { /* ... */ },
getWeightData : function () { /* ... */ }
};
App.getStuff = function (callback) {
$.when(
App.getExtraData(),
App.getColorData(),
App.getSizeData(),
App.getWeightData()
)
.then(function (extraData, colorData, sizeData, weightData) {
var context,
handleStuff = callback;
// do something to make all kinds of data become a single object
handleStuff( context );
});
};
App.handleStuff = function (stuff) { /* ... */ };
/// RUN
view = App.getStuff( App.handleStuff );
I did not expect the code in my example above to work, it is for illustrative purposes.
I've been trying to solve this for quiet a long time and it still gives no result. The documentation for jQuery.Deferred() and discussions around this, unfortunately, did not help me. So, I would be very glad and greatful for any help or advise.
Conceptually, you would use a counter that gets incremented as each asynchronous call completes. The main caller should proceed after the counter has been incremented by all the asynchronous calls.
I think what you're looking for are Promises / Deferreds.
With promises you can write something like:
when(getColorData(), getSizeData(), getWeightData(), getExtraData()).then(
function (colorData, sizeData, weightData, extraData) {
handle(/*..*/);
}
)
The get*Data() functions will return a promise that they fulfill when their assynchronous call is complete.
Ex:
function getData() {
var promise = new Promise();
doAjax("getData", { "foo": "bar" }, function (result) {
promise.resolve(result);
});
return promise;
}
The when simply counts the number arguments, if all it's promises are resolved, it will call then with the results from the promises.
jQuery has an OK implementation: http://api.jquery.com/jQuery.when/
What I could suggest for this scenario would be something like that.
write a function like this
var completed = 0;
checkHandler = function() {
if(completed == 4) {
handle(data);
}
}
where completed is the number of positive callbacks you must receive.
As soon as every function receives a callback you can increment the "completed" counter and invoke the checkHandler function. and you're done!
in example
function getColorData() {
$.get('ajax/test.html', function(data) {
completed++;
checkHandler();
});
}

Javascript callback function and parameters [duplicate]

This question already has answers here:
Pass an extra argument to a callback function
(5 answers)
Closed 6 years ago.
I want to something similar to this:
function AjaxService()
{
this.Remove = function (id, call_back)
{
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
call_back(res);
}
}
so my calling program will be like this:
var xx = new AjaxService();
xx.Remove(1,success);
function success(res)
{
}
Also if I want to add more parameters to success function how will I achieve it.
Say if I have success function like this:
var xx = new AjaxService();
//how to call back success function with these parameters
//xx.Remove(1,success(22,33));
function success(res,val1, val2)
{
}
Help will be appreciated.
Use a closure and a function factory:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
xx.Remove(1,generateSuccess(val1,val2));
What you're passing here is not the generateSuccess function but the anonymous function returned by generateSuccess that looks like the callback expected by Remove. val1 and val2 are passed into generateSuccess and captured by a closure in the returned anonymous function.
To be more clear, this is what's happening:
function generateSuccess (var1,var2) {
return function (res) {
// use res, var1 and var2 in here
}
}
var success = generateSuccess(val1,val2);
xx.Remove(1,success);
Or if you prefer to do it inline:
xx.Remove(1,(function(var1,var2) {
return function (res) {
// this is your success function
}
})(val1,val2));
not as readable but saves you from naming the factory function. If you're not doing this in a loop then Xinus's solution would also be fine and simpler than my inline version. But be aware that in a loop you need the double closure mechanism to disconnect the variable passed into the callback function from the variable in the current scope.
You can pass it as anonymous function pointer
xx.Remove(1,function(){
//function call will go here
success(res,val1, val2);
});
one way to do this:
function AjaxService {
var args_to_cb = [];
this.Remove = function (id, call_back, args_to_callback_as_array) {
if( args_to_callback_as_array!=undefined )
args_to_cb = args_to_callback_as_array;
else
args_to_cb = [];
myWebService.Remove(id, CallBack)
}
function CallBack(res) {
setTimeout( function(){ call_back(res, args_to_cb); }, 0 );
}
}
So you can use it like this:
var service = new AjaxService();
service.Remove(1,success, [22,33]);
function success(res,val1, val2)
{
alert("result = "+res);
alert("values are "+val1+" and "+val2);
}
I usually have the callback execute using a setTimeout. This way, your callback will execute when it gets the time to do so. Your code will continue to execute meanwhile, e.g:
var service = new AjaxService();
service.remove(1, function(){ alert('done'); }); // alert#1
alert('called service.remove'); // alert#2
Your callback will execute after alert#2.
Of course, in case of your application, it will happen so automatically since the ajax callback itself is asynchronous. So in your application, you had better not do this.
Cheers!
jrh

Categories