How to get all arguments of a callback function - javascript

Very often, I find myself using a callback function and I don't have its documentation handy, and it would be nice to see all of the arguments that are meant to be passed to that callback function.
// callback is a function that I don't know the args for...
// and lets say it was defined to be used like: callback(name, number, value)
something.doSomething( callback );
How can I determine what args its passing into that?
Note: looking at the source code can be unhelpful when the code itself is obfuscated and minified (as many js frameworks are)

To get the list of arguments without breaking functionality, overwrite the callback function in this way:
var original = callback;
callback = function() {
// Do something with arguments:
console.log(arguments);
return original.apply(this, arguments);
};
The context, this is preserved.
All arguments are correctly passed.
The return value is correctly passed.
NOTE: This method works in most cases. Though there are edge cases where this method will fail, including:
Read-only properties (e.g. defined using Object.defineProperty with writable:false)
Properties that are defined using getters/setters, when the getter/setter is not symmetric.
Host objects and plugin APIs: E.g. Flash and ActiveX.

Could it be as easy as
function callback() {
console.log(arguments);
}
?
Every function provides the arguments it has been called with in the automagic arguments collection.

Isn't this sort of the cart leading the horse?
Your function takes a callback. It's the method using your function that should be aware of what arguments the callback should accept.

You can even tell it which function's args to get using [functionName].arguments:
(function(arg1, arg2, agr3){
console.log('args are:', arguments);
return function fn(){
function m(){
console.log(
'fn.arguments:', fn.arguments,
'm.arguments:', m.arguments,
'argumentsX:', arguments
);
};
m('mArg1', 'mArg2', 'mArg3', 'mArg4');
};
})
(1, 2, Math.PI) // invoke closure
('fnArg1', 'fnArg2', 'fnArg3', 'fnArg4'); // invoke "fn"
Every function def rewrites the the arguments keyword to be of that scope btw (as seen with the "argumentsX" log).

Related

how does the returned function of JavaScript debounce function get all parameters through arguments

generally for js debounce function, a simple implement goes like
function debounce(func, wait) {
let timerId = null;
return function (...args) {
console.log('args',args);
clearTimeout(timerId);
timerId = setTimeout(() => func.apply(this, args), wait);
}
}
and when you use it, you can do things like
var random = function() {console.log(arguments)};
var test = debounce(random, 1000);
test(1,2,3);
my question is, how does the returned function inside debounce function can get all attributes that gets passed into test function (here being 1,2,3) through arguments object? I feel like it might have to do with closure, but can anyone explain?
I've also created a jsFiddle for simpler view
https://jsfiddle.net/u4n07veb/22/
Another question would be in this js fiddle, my console.log args can print 1,2,3, since 1,2,3 is what I pass to test function, but would it also be possible to get 4,5,6 inside the debounce function? Since 4,5,6 is the parameters I pass to the callback function of the debounce function?
Though the arguments special object does exist in JavaScript, in the implementation of debounce above the function arguments are instead retrieved via a rest parameter.
The difference is minimal - arguments is not a true Array but instead an Array-like object, whereas the ...args rest parameter method will retrieve an actual Array - but it's one worth mentioning.
The actual passing through of these arguments happens when Function.prototype.apply is called, which allows a function to be called with a given value of this and a specified array of arguments. It's a sibling to the similar Function.prototype.call, which takes each argument to passed through as a separate parameter.
So in your example, you call test(1, 2, 3) and that executes the function that was returned by debounce(random, 1000). That function gets its arguments as an Array via the ...args rest parameter, and then passes that array through to the random function via func.apply(this, args).
To answer your question about passing a different set of parameters through, I recommend you try it and see. But the answer is yes: with this setup the debounced function is able to pass through any number of arguments.
Closures aren't directly relevant to how the arguments are passed through here. They are relevant in a different way, however, in that the timerId variable created when debounce is called is kept in a closure so that later attempts to call the debounced function will access it again, which is what allows the innards of this debounce implementation to clear the timeout it had created during its previous execution.

Are function-arguments not necessarily objects?

I'm learning functional programming and node.js, and I came across this odd problem when using Function.prototype.apply and .bind.
function Spy(target, method) {
var obj = {count: 0};
var original = target[method]
target[method] = function (){//no specified arguments
obj.count++
original.apply(this, arguments)//only arguments property passed
}
return obj;
}
module.exports = Spy
This code works, it successfully spies on target.method.
//same code here
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
//same code here
This code, however, does not. It gives an error message: TypeError: CreateListFromArrayLike called on non-object.
And then the biggest surprise is, this method works perfectly fine.
//same code here
target[method] = function (args){
obj.count++
original.bind(this, args)
}
//same code here
So why exactly do I get this error? Is it because function arguments are not necessarily objects? Or is it because apply has a stricter description than bind?
In this version:
target[method] = function (args){//args specified
obj.count++
original.apply(this, args)//and passed here
}
Here you are not taking all the arguments but just one, named args. Since apply expects an array like object you cannot use args since it is only the first argument passed to the original target.
You can change it to:
target[method] = function (arg){ //only one argument specified
obj.count++
original.apply(this,[arg]) //one argument passed here
}
Now it works, but you can only spy on one argument functions. Using call would be better since you only have one extra argument:
target[method] = function (arg){ //only one argument specified
obj.count++
original.call(this,arg) //one argument passed here
}
Now bind is a totally different animal. It partial applies functions, thus return functions. Imagine you need to send a callback that takes no arguments but calls a function with some arguments you have when making it. You see code like:
var self = this;
return function() {
self.method(a, b);
}
Well. bind does this for you:
return this.method.bind(this, a, b);
When calling either of these returned functions the same happens. The method method is called with the arguments a and b. So calling bind on a function returns a partial applied version of that function and does not call it like call or apply does.
bind is called the same way as call is, even though they do very different things.
If you really wanted to use bind in this way. You could use the spread operator (ES2015) to expand the arguments 'array' to individual arguments:
original.bind(null, ...args);
That will bind the original function with the array values as individual arguments.

In JavaScript, must bound parameters be in the anonymous function parameters?

Consider the code below, which works fine. It is the outcome of some debugging. It appears to work because I have inlcluded selectedRowNum not only in bind but it seems I am also required to include selectedRowNum as a parameter to the anonymous callback that is run by .end
Does this make sense? If I bind a variable, must I also include it as a param to the function I am binding it to?
for (var i = selectedRows.length; i--;) {
var selectedRowNum = selectedRows[i];
console.log('outer selectedRowNum');
console.log(selectedRowNum);
var url = urlbase + '/' + this.state.data[selectedRowNum].id;
request
.del(url)
.end(function(selectedRowNum, err, res) {
var data = this.state.data.slice();
data.splice(selectedRowNum, 1);
this.setState({data: data});
this.forceUpdate();
}.bind(this, selectedRowNum));
};
Yes, you need to.
The args values passed to the bind() will be prepended to the called functions param list, so you need to receive it in the target function as arguments.
A simple example will be
function x(p1, p2, p3) {
console.log(p1, p2, p3)
}
var fn = x.bind(window, 1);
fn(2, 3);
fn('a', 'b');
where we are passing an additional param 1 to the bind and when the binded function is called we are passing 2 and 3, now when we receive it in the method we need to have 3 parameters there.
How else do you expect to reference the bound value that you're trying to pass in?
function(selectedRowNum, err, res) { here selectedRowNum is simply a reference to the first argument that's passed in.
Well, technically the answer is no you don't need to. You could not list it as an argument, and refer to it as arguments[0]
Arguments are not absolutely required to be in the anonymous function declaration because you can access arguments via the arguments object as in arguments[0] and arguments[1].
But it is best to declare them as named arguments because it makes your code easier to read and write. If you don't declare them as named arguments in the anonymous declaration, then there is no symbolic name by which to refer to them and you are forced to use the arguments object to access them.
This is an important aspect of Javascript. Declaring named function arguments in a function declaration (whether anonymous or not) just gives you name by which you can refer to that specific argument. Since having a symbolic name is an important part of creating readable code, it is generally considered a good practice to follow.
When you use .bind() with arguments in addition to just the first argument, you are prepending more arguments to the actual function call. This will shift any other arguments later in the argument list. In order to access the correct argument, the anonymous function declaration must use the variables from the right spot in the argument list. If you don't include the extra variables that are added via .bind(), then your other arguments will be out of position and will have the wrong values.

Eventmanager that calls other objects' functions

I'm using javascript and jQuery. I have a "problem" that my web app has different views, and one action (like mouse click) should do different things in the different views.
I've created an ActionManager that get's notified when an click event is fired. This manager object knows about the current view. Now I wan't the actionmanager to be able to trigger a function in different objects.
What I've done is that the manager gets an instance of the different objects. This is done via a registration when they are initialized, where they register what kind of event they wan't to handle and what kind of view they are responsible for.
the problem is that I want to make this as generic as possible, so that the actionmanager doesn't need to know about what the name of the function that is gonna be called is. Then the different instances can have different function names without letting the actionmanager to know about it. I could let the registration part store the function name for that event in that instance by sending a reference to the function, but then I can't do this:
myfunc : function (myinstance, myfunction)
{
myinstance.myfunction();
}
that will assume that myfunction() exists as a function in myinstance, and not use the real function name which could be "onMouseClick".
Edit:
Explanation for others seeing this tread and wondering why: The reason for my eventmanager is that I want to only add one click event on the elements that need it, and not changing the click event code. I also want to only call one method and run the code in that method. I don't want to call several methods and let the methods decide based on the view if they should run or not.
If myfunction is a string:
You can do this:
myfunc : function (myinstance, myfunction)
{
myinstance[myfunction]();
}
A function on an object is simply a function object assigned to a property on that object. In JavaScript, you can access properties in one of two ways:
Using dotted notation and a literal for the property name, e.g. x = obj.foo;, or
Using bracketed notation and a string for the property name, e.g. x = obj["foo"];
They do exactly the same thing, but of course the second form is much more flexible — designed, in fact, for exactly the situation you're describing.
If myfunction is an actual function object:
You can do this:
myfunc : function (myinstance, myfunction)
{
myfunction.call(myinstance);
}
That calls myfunction and ensures that this = myinstance during the call. All JavaScript function objects have a call function and an apply function. They both do the same thing (call the function with a specific this value), but they differ in how you pass arguments to the function:
With call, you pass them as discrete arguments in the call call:
func.call(inst, arg1, arg2, arg3);
With apply, you pass in an array of arguments:
func.apply(inst, [arg1, arg2, arg3]);
// ^----------------^---- note that this is an array
E.g.:
var a = [arg1, arg2, arg3];
func.apply(inst, a);
Example of all of the above
Live copy - Since you said you were using jQuery, I went ahead and used it for convenience, but none of the above is related to jQuery, it's how vanilla JavaScript works.
var myinstance = {
foo: "bar",
myfunction: function(arg1, arg2) {
display("<code>myfunction</code> called, <code>this.foo</code> is: " + this.foo);
if (arg1) {
display("<code>arg1</code> is: " + arg1);
}
if (arg2) {
display("<code>arg1</code> is: " + arg2);
}
}
};
// Without arguments
callUsingString(myinstance, "myfunction");
sep();
callUsingFunction(myinstance, myinstance.myfunction);
sep();
// With arguments
callUsingStringWithArgs(myinstance, "myfunction", ["one", "two"]);
sep();
callUsingFunctionWithArgs(myinstance, myinstance.myfunction, ["one", "two"]);
sep();
function callUsingString(inst, func) {
inst[func]();
}
function callUsingFunction(inst, func) {
func.call(inst);
}
function callUsingStringWithArgs(inst, func, args) {
// Easier using the function reference:
callUsingFunctionWithArgs(inst, inst[func], args);
}
function callUsingFunctionWithArgs(inst, func, args) {
func.apply(inst, args);
}
(display just appends a paragraph element to the page with the given text; sep just appends an hr element.)
More reading:
Mythical methods
You must remember this
If myFunction is a string you can do it this way.
myfunc : function (myinstance, myfunction)
{
myinstance[myfunction]();
}
But if myFunnction is a function object, you have to call that function. Where the this object within the myFunction references to myinstance.
myfunc : function (myinstance, myfunction)
{
myfunction.call(myinstance);
}
You can do it this way, but why don't you use the .bind() and the .trigger() to handle events? DEMO http://jsfiddle.net/DnjRs/

When passing a callback function, can you set a parameter?

I am passing a callback function as a parameter to a function, and I am doing this in various parts of my web app.
I want the callback to react a little differently in some cases, can I pass a parameter somehow to this callback?
soemethod(callback);
otherethod(callback);
otherother(callback(a=1));
How could I pass a=1 in the callback?
Simply use an anonymous function wrapped around your parameterized function call:
otherother(function () {
callback(1); // assuming the first parameter is called a
});
No, you can't.
But you can do something like this:
soemethod(callback);
otherethod(callback);
otherother(callback, 1);
function otherother(callback, defaultValue) {
var value = defaultValue;
// your logic here, ie.
if(someCondition)
value = 2;
callback(value);
}
As others already mentioned, you can't pass default parameters like that in Javascript - you have to create the separate function yourself.
What you can do however, is use some very neat helper functions to create these closures automatically for you. One of the patterns I like the most is partial function application, where the "default" parameters are the leftmost parameters.
If you are using a new browser you can use Function.prototype.bind (it also handles the this parameter - this can allow passing methods as callbacks as well)
otherother( callback.bind(undefined, 1) );
//sets the first parameter to 1
//when the callback is called, the 2nd, 3rd, parameters are filled and so on
If you need to support older browsers as well, it is not hard to create your own partial application functions (lots of JS frameworks have one of some sort, the next example was taken from Prototype)
Function.prototype.curry = function() {
var fn = this, args = Array.prototype.slice.call(arguments);
return function() {
return fn.apply(this, args.concat(
Array.prototype.slice.call(arguments)));
};
};

Categories

Resources