Javascript: Passing custom arguments to a callback function - javascript

I have this callback function setup:
var contextMenu = [];
var context = [ { "name": "name1", "url": "url1" }, {"name": name2", "url: "url2" } ];
for(var i=0; i < context.length; i++) {
var c = context[i];
var arr = {};
arr[c.name] = function() { callback(c.url); }
contextMenu.push( arr );
}
function callback(url) {
alert(url);
}
The problem is that the url value passed to the callback is always the last value in the context variable - in this case "url2". I am expecting to pass specific values to each "instance" of the callback, but as the callback seems to be remember the same value, the last time it was referred.
I am kind of stuck. Any help would be appreciated.
PS: I am using jQuery ContextMenu which, to my understanding, does not support sending custom data to its callback functions. It is in this context that I have this problem. Any suggestions to overcome in this environment is also helpful!

Use an additional closure.
arr[c.name] = (function(url) {
return function() { callback(url); }
})(c.url);
See Creating closures in loops: A common mistake and most other questions on this topic, and now your question is also added to this pool.

You are creating a series of closure functions inside the for loop
arr[c.name] = function() { callback(c.url); }
and they all share the same scope, and hence the same c object which will point to the last element in your array after the loop finishes.
To overcome this issue, try doing this:
arr[c.name] = function(url) {
return function() { callback(url); };
}(c.url);
Read more about closures here: http://jibbering.com/faq/notes/closures/

General solution
Callback creator helper
I created a general callback creator along the Creating closures in loops: A common mistake that Anurag pointed out in his answer.
Parameters of the callback creator
The function's first parameter is the callback.
Every other parameter will be passed to this callback as parameters.
Parameters of the passed callback
First part of the parameters come from the arguments you passed to the callback creator helper (after the first parameter as I described previously).
Second part comes from the arguments that will be directly passed to the callback by its caller.
Source code
//Creates an anonymus function that will call the first parameter of
//this callbackCreator function (the passed callback)
//whose arguments will be this callbackCreator function's remaining parameters
//followed by the arguments passed to the anonymus function
//(the returned callback).
function callbackCreator() {
var functionToCall = arguments[0];
var argumentsOfFunctionToCall = Array.prototype.slice.apply(arguments, [1]);
return function () {
var argumentsOfCallback = Array.prototype.slice.apply(arguments, [0]);
functionToCall.apply(this, argumentsOfFunctionToCall.concat(argumentsOfCallback));
}
}
Example usage
Here is a custom AJAX configuration object whose success callback uses my callback creator helper. With the response text the callback updates the first cell of a row in a DataTables table based on which row the action happened, and prints a message.
{
url: 'example.com/data/' + elementId + '/generate-id',
method: 'POST',
successHandler: callbackCreator(function (row, message, response) {//Callback parameters: Values we want to pass followed with the arguments passed through successHandler.
table.cell(row, 0).data(JSON.parse(response).text);
console.log(message);
},
$(this).parents('tr'),//Row value we want to pass for the callback.
actionName + ' was successful'//Message value we want to pass for the callback.
)
}
Or in your case:
arr[c.name] = callbackCreator(function(url) {
callback(url);
},
c.url
);

Related

JavaScript: Callback Accessing another parameter

In this example it seems that a callback is accessing another parameter (without having to provide the argument again).
Excerpt from the link above
var SimplePropertyRetriever = {
getPrototypeEnumerables: function(obj) {
return this._getPropertyNames(obj, false, true, this._enumerable);
},
_enumerable: function(obj, prop) {
return obj.propertyIsEnumerable(prop);
},
_getPropertyNames: function getAllPropertyNames(obj, iterateSelfBool, iteratePrototypeBool, includePropCb) {
...
}
}
As seen:
this._enumerable is passed to _getPropertyNames
_enumerable accepts a parameter called obj
obj is not explicitly passed down though, so it seems that when the callback is passed to _getPropertyNames it somehow accesses its first argument, which is obj
To test it, I tried the below, which did not work.
function myFunc2(para, callback) {
console.log(`Para: ${para}`);
callback();
}
myFunc2(42, (para) => console.log(para));
Any idea what I am missing here?
I would suggest you understand Closures and Variable Scopes in JavaScript.
For the code snippet you mentioned above, since your callback needs an arg 'para' you can pass it and log it. If you don't want to pass it you can just add it to global object( 'window' object in case of browser).
This is because your anonymous callback function expects 'para' variable first in it's own body, next it will try to search in it's parent's body which in this case is global object.
For the below example,
function myFunc2(para, callback) {
window.para = para;
console.log(`Para: ${para}`);
callback();
}
myFunc2(42, () => console.log(para));

using a thunk to factor time out of async code

Kyle Simpson has an amazing class on pluralsight.
In one of the modules, he goes through a snippet of code that can be safely called asynchronously, and be certain that the results are going to be shown to the user in the same sequence with which the code was executed.
The function in its glory:
function getFile(file) {
var text, fn;
fakeAjax(file, function(response){
if (fn) fn(response);
else text = response;
});
return function(cb) {
if (text) cb(text);
else fn = cb;
}
}
We can call it like so:
I'm having a tough time understanding the getFile function:
where is cb defined? how does it get called, i.e. cb(text) if it's not defined anywhere?
when we call getFile, how does the response get a value at all?
getFile returns an anonymous function:
return function(cb) {
if (text) cb(text);
else fn = cb;
}
so var th1 = getFile("file") ends up assigning that anonymous function to the value of th1, so th1 can now be called with an argument - which becomes cb. So when later, we call th1 with:
th1(function(text1) {
...
we are passing in a second anonymous function (with argument text1) which is assigned to cb (shorthand for 'callback').
The reason it works is that when the ajax is complete, it does one of two things:
if fn is defined, calls fn with the response
if not, it stores the response
Conversely, when the returned anonymous function is called, it does one of two things:
if text is defined (i.e. a result is already received) then it calls the callback with the response
if not, it assigns the callback (cb) to fn
This way, whichever happens first - ajax complete, or thunk called, the state is preserved, and then whichever happens second, the outcome is executed.
In this way, the 'thunks' can be chained to ensure that while the ajax functions happen in parallel the output methods are only called in the sequence in which the fn values are assigned.
I think part of the confusion is unclear variable naming, and the use of liberal anonymous functions with out giving them an intention revealing name. The following should be functionally equivalent with clearer naming (I think):
function getFile(file) {
var _response, _callback;
fakeAjax(file, function(response){
if (_callback) _callback(response);
else _response = response;
});
var onComplete = function(callback) {
if (_response) callback(_response);
else _callback = callback;
}
return onComplete;
}
then:
var onFile1Complete = getFile("file1");
var onFile2Complete = getFile("file2");
var onFile3Complete = getFile("file3");
var file3Completed = function(file3Response) {
output("file3Response");
output("Complete!");
}
var file2Completed = function(file2Response) {
output(file2Response);
onfile3Complete(file3Completed)
}
var file1Completed = function(file1Response) {
output(file1Response);
onFile2Complete(file2Completed);
}
onFile1Complete(file1Completed);

Invoke a JavaScript function with parameter in a function

Today, I saw the following code below:
log_execution_time = require('./utils').log_execution_time;
var fib = function fib(n) {
if (n < 2) return n;
return fib(n - 1) + fib(n - 2);
};
var timed_fib = log_execution_time(fib);
timed_fib(5);
>>> Execution time: 1.166ms
I am curious about function log_execution_time. I don't know how it is.
You can see the input of log_execution_time is a function. How can it call the function with parameter? But all of the methods from w3school need a parameter when calling a function. I assume:
var log_execution_time = function (input_function){
console.time("Execution time");
// input_function
console.timeEnd("Execution time");
}
Thanks and regards
I think the OP is specifically about how the 5 parameter gets passed to the function input_function
Functions are first class objects in JavaScript. You can set identifiers and pass their references around just the same as any other object.
log_execution_time(fib); does not invoke fib, it passes a reference to fib into the log_execution_time function as the first argument. This means the internals can reference fib
timed_fib is a function which can reference the closure from that invocation of log_execution_time due to when it was created, so it can hence invoke the reference to fib as desired
Here is a simple example;
function log(msg) {
console.log(msg);
}
function wrap(fn) {
return function () { // some anonymous function to be our wrapper
console.log('Wrapped:');
fn.apply(this, arguments); // this line invokes `fn` with whatever arguments
// that were passed into the anonymous function
};
}
var foo = wrap(log);
foo('Hello World'); // logs
// Wrapped:
// Hello World
We could also have used the more usual way to invoke fn, for example fn("fizz buzz");, instead of .apply but that would mean we needed to know more about how to invoke fn, which could have been anything
Useful stuff:
Function.prototype.apply
Function.prototype.call
This is known as function currying, in this case the function is being curried with a parameter that also happens to be a function. It may look something like this:
function logTime(f) {
return function() {
var s = new Date();
var r = f.apply(null, arguments);
var e = new Date();
console.log('Time taken ' + (e-s));
return r;
}
}
function numberlogger(n) {
console.log("logged number: " + n)
};
var timedlogger = logTime(numberlogger);
console.log(timedlogger(2));
We call logTime, passing in numberlogger as an argument. Functions in JavaScript are objects and can be passed around like anything else. The logTime function returns a different function that is then stored in timedlogger. When we invoke timedlogger, we're actually invoking the function that logTime returned. That uses a couple of variables to keep track of the start and end times for timing, but uses apply (which every function in js has) to call the original function (numberlogger) whilst passing in any arguments supplied.
I suggest reading up on Functions in Javascript. Here's a nice article from the Mozilla Developer Network (MDN) which is in my opinion, a much better resource than w3schools
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Functions
To answer your question though, functions in javascript are first class citizens, and what that means is that you can think of them as any other object (string,boolean,number etc). They can be saved in variables and they can be passed as arguments into other functions.
In your example, log_execution_time will actually return a function which is essentially a wrapper around the fib function that gets passed to it
The code can be like this:
var log_execution_time = function (input_function){
var f=function(args)
{
var t1=new Date().getTime();
input_function(arguments);
console.warn("Execution time:" +(new Date().getTime()-t1).toString());
}
return f;
}

How to pass params to a callback

I'm trying to find the best practice of implementing the following:
I have 3 functions: manager, httpGet and onsuccess:
manager calls the httpGet function and passes the
onsuccess function to be called upon success.
httpGet makes a request and invokes onsuccesss
onsuccess uses needs params from manager
What is the best way to pass and argument from manager to onsuccess without involving httGet?
I was thinking about passing a params object to httpGet which in turn will be passed to onsuccess but I really don't like the idea of passing params to a function that doesn't use them at all.
You mean something like this:
function manager()
{
var managerParam =0;
//the anonymous function below is in the scope of manager() so has
//access to all of managers parameters
httpGet(function() {
//this function can use managerParam
managerParam ++;
});
console.log(managerParam === 1); //true
}
function httpGet(onsuccess)
{
onsuccess();
}
Fiddle
Use a closure:
data = { cool: true };
var yourCallbackFunction = function(httpData, notHttpData){};
var bakedCallback = (function() {
var _data = data;
return function(httpResp) {
yourCallbackFunction(httpResp, _data);
}
})();
httpGet.onSuccess(bakedCallback);
data is the extra data you want to pass to the function.
In your callback:
httpData is the data received from request.
NOThttpData is the extra data.
Define onsuccess as local function in manager, it may use its arguments and local variables.
Pass onsuccess to httpGet.

Passing Params to a Window[callback] Function

I have some code that requests some JSON from an API. When data is returned, per the documentation, it sends back a callback function that is to be used to parse the data at the top level. After the call is made, I have the following code to capture the data and process it:
var callback = 'functionUsedInApiCall';
window[callback] = newCallBackFunction;
How would I go about passing custom params to the callback function above as the data is being returned?
In order to capture the data, I must write the callback function like this:
function newCallBackFunction(root) {
//root is the data
}
Any help would be greatly appreciated.
Are you talking about JSONP? If so, you don't call the callback or pass in the argument at all, the code returned by the API does.
E.g., your code:
window.myCallback = newCallbackFunction;
function newCallbackFunction(data) {
// use the data
}
(I'm assuming this isn't at global scope, hence assigning to the window object.)
...plus your code for initiating the JSONP call, which is usually appending a script element to your page with a URL containing the name of the callback ("myCallback" in the above).
Their response will look like this:
myCallback({
// data here
});
...which, when it arrives, will run (because it's the content of a script element), and will call your function. This is how JSONP works.
If you want to include further arguments for the function, all you do is have the callback they call turn around and call your target function, e.g.:
window.myCallback = function(data) {
newCallbackFunction(data, "foo", "bar");
};
function newCallbackFunction(data) {
// use the data
}
Now when their code calls the global myCallback, all it does is turn around and call newCallbackFunction with that data and the arguments you specify.
Those arguments don't have to be literals as in the above. Here's an example with a bit more context, using a closure:
// Assume the url already contains the name of the callback "myCallback"
function doJSONP(url, niftyInfo, moreNiftyInfo) {
var script;
// Set up the callback
window.myCallback = function(data) {
// Runs when the data arrives
newCallbackFunction(data, niftyInfo, moreNiftyInfo);
};
// Trigger the request
script = document.createElement('script');
script.src = url;
document.documentElement.appendChild(script);
}
Ideally, though, when doing JSONP you auto-generate the name of the callback each time so that it's specific to the request (in case you have two outstanding requests at the same time):
// Assume the url ends with "callback=" and we append the name of the
// callback function to it
function doJSONP(url, niftyInfo, moreNiftyInfo) {
var cbname, script;
// Get a callback name
cbname = "callback_" +
new Date().getTime() +
"_" +
Math.floor(Math.random() * 10000);
// Set up the callback
window[cbname] = function(data) {
// Remove us from the window object
try {
delete window[cbname];
}
catch (e) { // Handle IE bug (throws an error when you try to delete window properties)
window[cbname] = undefined;
}
// Runs the function
newCallbackFunction(data, niftyInfo, moreNiftyInfo);
};
// Trigger the request
script = document.createElement('script');
script.src = url + encodeURIComponent(cbname);
document.documentElement.appendChild(script);
}
Parameters in javascript are passed as an Array, so you can pass the parameters you need, or even complete functions that will add per case functionality in your callback.
You could do the following:
function newCallBackFunction(root /*your data*/, paramsHash /*a hash array with optional parameters*/)
{
//if arg1 can be found in the hash
if( paramsHash['arg1'] ]
{
//do something that requires arg1
}
else if( paramsHash['arg2'] )
{
//do something that requires arg2
}
return root;
}
And in your main code:
var hash = new Array();
hash['arg1'] = 'str1';
hash['arg2'] = 1;
hash['arg3'] = new Car(); //or any other object you want
It is also possible to just declare some parameters and supply them to your function only when needed:
function newCallBackFunction(root, param1, param2)
{
if( param1 ) { /* similar to first example */ }
}
Or finally just pass whatever parameter you want and read them from the arguments table
function newCallBackFunction(root)
{
for( int i = 1; i < arguments.length; i++ )
//do something with the parameters you pass beside root
}
And in main code:
newCallBackFunction( root, param1, param2, param3 );
I hope I covered you!

Categories

Resources