JavaScript: Callback Accessing another parameter - javascript

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));

Related

Why does the anonymous callback work but the named callback fail?

I have the following code:
this.leaves.forEach(this.leafFloat(leaf));
private leafFloat(leaf: Phaser.Physics.Arcade.Sprite): () => void {
return function() {
leaf.setVelocityX(-50);
}
}
But I get the error "Cannot find name 'leaf'".
If I make the callback an anonymous function like so:
this.leaves.forEach((leaf) => { leaf.setVelocityX(-50)});
It runs exactly as expected, why is this?
It's because in the first line you pass as a parameter the variable leaf, which is not defined previously.
this.leaves.forEach(this.leafFloat(leaf));
// ^- here
However, in the lambda function, you are calling the parameter leaf, which is correctly defined.
this.leaves.forEach((leaf) => { leaf.setVelocityX(-50)});
// ^-defined ^-called

How to pass a callback function with multiple possible parameter lists

Here are two callback function:
function callback_a(){
alert('a');
}
function callback_b(p){
alert('b says'+ p)'
}
If I want use callback_a
function test(callback){
if(condition){
callback();
}
}
test(callback_a);
But the function test isn't applicable to callback_b, So how to implement a common function that you can passing some callbacks function with multiple possible parameter lists.
There are three options:
The easiest way is to use spread operator:
function test(callback, ...callback_args) {
callback(...callback_args);
}
in this case the invocation of test for function callback_b would be like this:
test(callback_b,"b")
The second way is using arguments which are scoped to any function in JavaScript:
function test(callback) {
callback.apply(null, arguments.slice(1));
}
the invocation of test for function callback_b would be the same:
test(callback_b,"b")
Another options is to use partially applied functions. In this case you should define b_callback like this (ES6 syntax):
let callback_b = (p) => () => void{
alert('b says'+ p)'
}
or without ES6:
function callback_b(p) {
return function(){
alert('b says'+ p)'
}
}
and invoke it like this:
test(callback_b("b"))
There is a special object called arguments that gets created when a function is invoked. It's an array-like object that represents the arguments passed in to a function:
It can be used like this:
test();
// no arguments passed, but it still gets created:
// arguments.length = 0
// arguments >> []
test(a);
// ONE argument passed:
// arguments.length = 1
// arguments >> [a]
test(a,b,c,d);
// FOUR arguments passed:
// arguments.length = 4
// arguments >> [a,b,c,d]
Knowing this, one can call a callback with the rest of the arguments passed in from the parent function using apply like this:
function test(callback) {
callback.apply(null, Array.prototype.slice.call(arguments, 1));
}
// arguments passed into test are available in the function scope when
// .slice is used here to only pass the portion of the arguments
// array relevant to the callback (i.e. any arguments minus the
// first argument which is the callback itself.)
//
// N.B. The arguments object isn't an array but an array like object so
// .slice isn't available on it directly, hence .call was used here)
Might be worth reading up on:
The arguments object
Function.prototype.apply, Function.prototype.call and Function.prototype.bind as they are way to bind a context and arguments to a function (i.e. they'll work with the arguments object to call a function where you may not know how many arguments will be passed)
So how to implement a common function that you can passing some callbacks function with multiple possible parameter lists.
Basically, you don't. The function receiving the callback is in charge of what the callback receives as arguments. When you call Array#forEach, it's Array#forEach that decides what arguments your callback gets. Similarly, String#replace defines what it will call its callback with.
Your job is to say what test will do, what it will call its callback with. Then it's the job of the person using test to write their callback appropriately. For instance: You might document test as calling the callback with no arguments. If the caller wants to use callback_b, then it's up to them to handle the fact that callback_b expects a parameter. There are several ways they can do that:
The could wrap it in another function:
test(function() {
callback_b("appropriate value here");
});
...or use Function#bind
test(callback_b.bind(null, "appropriate value here"));
...but it's their problem, not yours.
Side note: If they pass you callback_b and you call it without any arguments, you won't get an error. JavaScript allows you to call a function with fewer arguments than it expects, or more. How the function handles that is up to the author of the function.
You can pass an anonymous function as the callback that will itself return your desired callback function with parameters.
test(function() { return callback_b(' how are you'); });
see this working snippet that will first use callback_a, then callback_b (with parameter) as the callback:
function callback_a(){
alert('a');
}
function callback_b(p){
alert('b says'+ p);
}
function test(callback){
if(true){
callback();
}
}
test(callback_a);
test(function() { return callback_b(' how are you'); });
You can pass the parameter while calling the callback
function test(callback){
if(condition){
callback();
}
else if(other condition){
callback("b");
}
}
test(callback_b);
You can write your callback function like
function callback_a_b(){
if(arguments.length){
var arg = [].slice.call(arguments);
alert('b says'+ arg[0])
}
else{
alert('a');
}
}
You can pass array of parameters as second param of test function or in ES6 use spread operator read more here
function test(callback, params){
if(condition){
if (params === undefined){
callback();
} else {
callback.apply(null, params); //params must be array
//ES6: callback(...params);
}
}
}
test(callback_a);
test(callback_b, [" whatever"]);
I've just checked in my browser (ffox 51.0.1) that the following works:
function test(callback,other_args){if(condition){callback(other_args);}}
results:
condition=true
test(callback_a)
=> shows the alert with 'a'
condition=false
test(callback_a)
=> doesn't show anything
condition=true
test(callback_b,"pepe")
=> shows the alert with 'b sayspepe'
condition=false
test(callback_b,"pepe")
=> doesn't show anything

How can I deal with optional parameters in JavaScript?

I would like a function 'get' that takes an id, an optional property parameter and a callback function. It would be used like so:
get(14297, 'name', function processName(name) {...});
get(14297, function processStudent(student) {...});
I have included one possible implementation below
function get(id, property, callback) {
var item = ...;
// property is callback
if (!callback) property(item);
// callback is callback
else callback(item[property])
}
It feels a little bit weird because
property(item);
Is actually a callback function depending on the context. Is there a better way to do this?
You should switch the parameters. Try this
function get(id, callback, property)
{
if(typeof property === "undefined")
callback(item);
else
callback(item[property]);
}
You can change the order of parameters, or test what the function is given to work out what they are. e.g.
function get(id, property, callback) {
if (arguments.length == 2) {
// second argument is callback
callback = property;
property = void 0;
}
...
}
or
function get(id, property, callback) {
if (typeof property == 'function') {
// second argument is callback
callback = property;
property = void 0;
}
...
}
and so on, but that type of overloading is not particularly liked.
This is the pattern used by jQuery:
function get(id, property, callback) {
// if the 2nd parameter is a function, assume that
// only two parameters were supplied
if (typeof property === 'function') {
callback = property;
property = undefined;
}
...
}
Effectively, if it sees unexpected parameter types it just shuffles their contents around until they match the alternative definition.
The arguments object is immutable. But you can slice it in an array, pop the last argument and deal with the other arguments as you normally would, since you know the callback argument isn't in it anymore.
Here is a way to do this:
function get() {
// Copy the `arguments` object in an array, since it's immutable
var args = Array.prototype.slice.call( arguments, 1 ),
// Pop the last argument of the arguments
callback = args.pop();
// Then deal with other arguments
// For example, check for the existence of the second argument
if ( args[1] ) {
}
// Then, you can call the callback function
callback();
}

Passing a function as an argument in a javascript function

I was wondering whether this is legal to do. Could I have something like:
function funct(a, foo(x)) {
...
}
where a is an array and x is an integer argument for another function called foo?
(The idea is to have one function that uses a for loop on the array, and calls that function in the params for every element in the array. The idea is so call this on different functions so elements of two arrays are multiplied and then the sums are added together. For example A[0] * B[0] + A[1] * B[1].)
I think this is what you meant.
funct("z", function (x) { return x; });
function funct(a, foo){
foo(a) // this will return a
}
This is not the way to declare a function with another function as one of it's parameters. This is:
function foodemo(value){
return 'hello '+ value;
}
function funct(a, foo) {
alert(foo(a));
}
//call funct
funct('world!', foodemo); //=> 'hello world!'
So, the second parameter of funct is a reference to another function (in this case foodemo). Once the function is called, it executes that other function (in this case using the first parameter as input for it).
The parameters in a function declaration are just labels. It is the function body that gives them meaning. In this example funct will fail if the second parameter wasn't provided. So checking for that could look like:
function funct(a, foo) {
if (a && foo && typeof a === 'string' && typeof foo === 'function'){
alert(foo(a));
} else {
return false;
}
}
Due to the nature of JS, you can use a direct function call as parameter within a function call (with the right function definition):
function funct2(foo){
alert(foo);
}
funct2(foodemo('world!')); //=> 'hello world!'
If you want to pass a function, just reference it by name without the parentheses:
function funct(a, foo) {
...
}
But sometimes you might want to pass a function with arguments included, but not have it called until the callback is invoked. To do this, when calling it, just wrap it in an anonymous function, like this:
funct(a, function(){foo(x)});
If you prefer, you could also use the apply function and have a third parameter that is an array of the arguments, like such:
function myFunc(myArray, callback, args)
{
//do stuff with myArray
//...
//execute callback when finished
callback.apply(this, args);
}
function eat(food1, food2)
{
alert("I like to eat " + food1 + " and " + food2 );
}
//will alert "I like to eat pickles and peanut butter"
myFunc([], eat, ["pickles", "peanut butter"]);
And what would you like it to achieve? It seems you mixed up a function declaration with a function call.
If you want to pass another calls result to a function just write funct(some_array, foo(x)). If you want to pass another function itself, then write funct(some_array, foo). You can even pass a so-called anonymous function funct(some_array, function(x) { ... }).
I would rather suggest to create variable like below:
var deleteAction = function () { removeABC(); };
and pass it as an argument like below:
removeETC(deleteAction);
in removeETC method execute this like below:
function removeETC(delAction){ delAction(); }
What you have mentioned is legal. Here, foo(X) will get called and its returned value will be served as a parameter to the funct() method
In fact, seems like a bit complicated, is not.
get method as a parameter:
function JS_method(_callBack) {
_callBack("called");
}
You can give as a parameter method:
JS_method(function (d) {
//Finally this will work.
alert(d)
});

Javascript: Passing custom arguments to a callback function

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
);

Categories

Resources