I am learning AngularJS and have copied code this basic code from a tutorial (simplified/pseudo code to only include the parts relevant to this question).
The code works for me, but I am trying to better understand how the argument is being passed the the callback in the success method.
// jobService object
var jobService = {
get : function() {
return $http.get( 'some/api/url' );
}
};
jobService.get().success(function (data) {
$scope.jobs = data;
});
My question is, knowing that "normally" arguments are specifically passed into the functions when invoked i.e.:
function foo(arg1) {
alert(arg1); //alerts Hello!
};
foo('hello!');
How is the data argument being passed into the anonymous callback function?
is it:
Being "injected" by AngularJS?
Does the javascript engine simply use variables on the local scope called data?
does the javascript engine look for a data property on the parent object if the success method?
TL;DR
We're just defining that anonymous function, not calling it!
Thus data is a function parameter, not a function argument.
Long version
Let's take this into little pieces.
success() is a function. It's chain-called after jobService.get(). So, whatever the jobService.get() call returns, we're calling the success function of that object (say returnedObject.success()).
Back to success() itself. It can easily read other properties of its object (returnObject from the example above). Since we're passing in the anonymous callback function as an argument, success can easily do something like (narrowing it down to basic JS):
function success(callback) {
var whatever = "I'm passing this to the callback function";
callback(whatever);
}
which would actually call our anonymous function we passed in, and assign it whatever as data (don't forget we're just defining that anonymous function, not calling it!) . This makes data a function parameter, and it is basically a custom name that you use to represent and access what the success function passes into its callback function. You can use whatever you want there - this would still work perfectly fine:
jobService.get().success(function (somethingElse) {
$scope.jobs = somethingElse;
});
Hope I didn't make this too complicated. I was trying to explain it step-by-step from the plain JS standpoint because you can easily read Angular's source to see what it does, so I thought you needed this simpler explanation.
Here's a basic example replicating what's going on there (inspect the JS source, see how the output is the same in all three cases):
var debug = document.getElementById('debug');
function success(callback) {
var whatever = 'hello world';
debug.innerHTML += '<br>success function called, setting parameter to <span>' + whatever + '</span><br>';
callback(whatever);
}
function callbackFunction(someParameter) {
debug.innerHTML += '<br>callbackFunction called with parameter <span>' + someParameter + '</span><br>';
}
success(callbackFunction);
// anon function
success(function(val) {
debug.innerHTML += '<br>anonymous callback function called with parameter <span>' + val + '</span><br>';
})
// anon function 2
success(function(anotherVal) {
debug.innerHTML += '<br>second anonymous callback function called with parameter <span>' + anotherVal + '</span><br>';
})
span {
color: green;
}
<div id="debug"></div>
An example using an object, similar to what is done in your original code:
var debug = document.getElementById('debug');
var myObject = {
whatever: 'hello world',
success: function(callback) {
debug.innerHTML += '<br>success function called, fetching object property and setting the parameter to <span>' + this.whatever + '</span><br>';
callback(this.whatever);
},
modifyMe: function() {
debug.innerHTML += '<br>object property modified<br>';
this.whatever = 'another world';
return this; // this is crucial for chaining
}
}
// anon function callback
myObject.success(function(val) {
debug.innerHTML += '<br>anonymous callback function called with parameter <span>' + val + '</span><br>';
})
debug.innerHTML += '<br><hr>';
// chaining - calling a success function on a modified object
myObject.modifyMe().success(function(val) {
debug.innerHTML += '<br>anonymous callback function called with modified parameter <span>' + val + '</span><br>';
})
span {
color: green;
}
<div id="debug"></div>
Here's the relevant part in the source code:
promise.success = function(fn) {
promise.then(function(response) {
fn(response.data, response.status, response.headers, config);
});
return promise;
};
Read more on GITHUB.
Related
I was watching this talk on the event loop in JavaScript and it explained how "callbacks" are executed. And I wrote this to see how it works:
function show(str) {
return 'Hello ' + str;
}
// This does not work
show('World', function (data) {
console.log(data);
});
// This works
console.log(show('Sayantan'));
Maybe I got this whole thing wrong. But how do I pass callbacks as parameters like the way I tried to do. For example in jQuery's $.get() or $.post() where we give a callback to do what we want to do after the response came back. So I hoping the function call would print 'Hello World' in console as that's what I defined in my callback. What am I doing wrong?
You're almost there - the only thing you didn't do is handle the callback in show:
function show(str,callback) {
callback('Hello ' + str); // this will execute the anonymous function with 'Hello ' + `str` as the variable
return 'Hello ' + str;
}
You could take the callback and call it in the function show with the wanted parameter.
function show(str, cb) {
return cb('Hello ' + str);
}
show('World', function (data) {
console.log(data);
});
Please help in understanding the below code:
// define our function with the callback argument
function some_function(arg1, arg2, callback) {
// this generates a random number between
// arg1 and arg2
var my_number = Math.ceil(Math.random() * (arg1 - arg2) + arg2);
// then we're done, so we'll call the callback and
// pass our result
callback(my_number);
}
// call the function
some_function(5, 15, function(num) {
// this anonymous function will run when the
// callback is called
console.log("callback called! " + num);
});
In the above code,what is the callback keyword.what is the use of this word.
Even there is no function defined with name callback.
The gap in logic I think you're having a hard time with is anonymous, unnamed functions. Once upon a time, all functions were named. So code was written like this:
function MemberProcessingFunction() {
// etc
}
function AdminProcessingFunction() {
// etc
}
var loginProcessingFunction;
if (usertype == 'BasicMember') {
loginProcessingFunction = MemberProcessingFunction;
}
else if (usertype == 'Admin') {
loginProcessingFunction = AdminProcessingFunction;
}
loginProcessingFunction();
Someone thought "This is dumb. I'm only creating those function names to use them in one place in my code. Let's merge that together."
var loginProcessingFunction;
if (usertype == 'BasicMember') {
loginProcessingFunction = function() {
// etc
};
}
else if (usertype == 'Admin') {
loginProcessingFunction = function() {
// etc
};
}
loginProcessingFunction();
This especially saves a lot of time when you're passing a function to another function as an argument. Often, this is used for "callbacks" - occasions where you want to run certain code, but only after a certain indeterminately-timed function has finished its work.
For your top function, callback is the name of the third argument; it expects this to be a function, and it is provided when the method is called. It's not a language keyword - if you did a "find/replace all" of the word "callback" with "batmanvsuperman", it would still work.
'callback' is a function passed as an argument to the 'some_function' method. It is then called with the 'my_number' argument.
when 'some_function' is actually being called as seen below
// call the function
some_function(5, 15, function(num) {
// this anonymous function will run when the
// callback is called
console.log("callback called! " + num);
});
it receives the whole definition of the third function argument. so basically your 'callback' argument has this value below
function(num) {
// this anonymous function will run when the
// callback is called
console.log("callback called! " + num);
}
To understand better how a function can be passed as an argument to another function, take a look at this answer.
This question already has answers here:
JavaScript: Passing parameters to a callback function
(16 answers)
Closed 7 years ago.
I needed to pass a parameter to a callback function in Javascript, so I did the following which creates an anonymous function as a string and then passes it:
var f = "var r = function(result) {do_render(" + i + ",result.name,result.data);}"
eval(f)
$.getJSON("analysis?file=" + getParameterByName('file') + "&step=" + i,r);
This doesn't seem like a great idea however. Is there a better way?
There's several techniques that you can use to do this. One of which is to create a new function which "seals off" one of the variables:
function myCallback(i, result) { ... }
function createCurriedFunction(i, func, context) {
return function (result) { func.call(context, i, result); }
}
for (i = 0; i < 5; i += 1) {
var curriedFunc = createCurriedFuncion(i, myCallback, this);
$.getJSON(url, curriedFunc);
}
Context is the object for which the "this" will refer to in the callback function. This may or may not be needed for what you're doing; if not you can just pass in null.
There's actually a function that does exactly that called bind, and is used like
var curriedFunc = myCallback.bind(this, i), which will seal off the first variable.
It looks like you are having issues closing over i and to solve it used eval. Instead of that, simply close over it using an immediately invoked function expression (IIFE) like this:
(function(i){
//create a closure of the value of i
//so that it takes the immediate value of it instead of the end value
//based on the assumption i is from a loop iterator
$.getJSON("analysis?file=" + getParameterByName('file') + "&step=" + i,
function(result){
do_render(i, result.name, result.data);
}
);
})(i);//pass i into the IIFE in order to save its immediate value
You can simply do
var url = "myurl";
$.getJSON(url, function(result){
//callback 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)
});
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
);