How to structure javascript callback so that function scope is maintained properly
With the code you have provided test
will still be in scope inside the callback. xhr
will not be, other than xhr.responseText
being passed in as data
.
Updated from comment:
Assuming your code looks something like this:
for (var test in testers)
getFileContents("hello"+test+".js", function(data) {
alert(test);
});
}
As this script runs, test
will be assigned the values of the keys in testers
- getFileContents
is called each time, which starts a request in the background. As the request finishes, it calls the callback. test
is going to contain the FINAL VALUE from the loop, as that loop has already finished executing.
There is a technique you can use called a closure that will fix this sort of problem. You can create a function that returns your callback function, creating a new scope you can hold onto your variables with:
for (var test in testers) {
getFileContents("hello"+test+".js",
(function(test) { // lets create a function who has a single argument "test"
// inside this function test will refer to the functions argument
return function(data) {
// test still refers to the closure functions argument
alert(test);
};
})(test) // immediately call the closure with the current value of test
);
}
This will basically create a new scope (along with our new function) that will "hold on" to the value of test
.
Another way of writing the same sort of thing:
for (var test in testers) {
(function(test) { // lets create a function who has a single argument "test"
// inside this function test will refer to the functions argument
// not the var test from the loop above
getFileContents("hello"+test+".js", function(data) {
// test still refers to the closure functions argument
alert(test);
});
})(test); // immediately call the closure with the value of `test` from `testers`
}
Callback/scope understanding
You're right - the function has access to the scope which it is defined in.
Your example is effectively this (moving your callback to its own function):
var greeting = 'hi';
function greet(callback) {
callback();
}
function logGreeting() {
console.log(greeting);
}
greet(logGreeting);
As you can see, logGreeting
has access to greeting
at this point because it is defined in the same (or higher) scope.
However, when we move greeting
into the greet
function:
function greet(callback) {
var greeting = 'hi';
callback();
}
function logGreeting() {
console.log(greeting);
}
greet(logGreeting);
greeting
is no longer in the same or higher scope to logGreeting
, it's in a different scope entirely, so logGreeting
cannot look upwards through the scope in order to find greeting
.
The scope which logGreeting
has access to is the scope in which it is defined rather than which it is called, so it doesn't evaluate the scope when it's called with callback()
like you mentioned in your question.
JavaScript Callback Scope
(extracted some explanation that was hidden in comments in other answer)
The problem lies in the following line:
this.dom.addEventListener("click", self.onclick, false);
Here, you pass a function object to be used as callback. When the event trigger, the function is called but now it has no association with any object (this).
The problem can be solved by wrapping the function (with it's object reference) in a closure as follows:
this.dom.addEventListener(
"click",
function(event) {self.onclick(event)},
false);
Since the variable self was assigned this when the closure was created, the closure function will remember the value of the self variable when it's called at a later time.
An alternative way to solve this is to make an utility function (and avoid using variables for binding this):
function bind(scope, fn) {
return function () {
fn.apply(scope, arguments);
};
}
The updated code would then look like:
this.dom.addEventListener("click", bind(this, this.onclick), false);
Function.prototype.bind
is part of ECMAScript 5 and provides the same functionality. So you can do:
this.dom.addEventListener("click", this.onclick.bind(this), false);
For browsers which do not support ES5 yet, MDN provides the following shim:
if (!Function.prototype.bind) {
Function.prototype.bind = function (oThis) {
if (typeof this !== "function") {
// closest thing possible to the ECMAScript 5 internal IsCallable function
throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
}
var aArgs = Array.prototype.slice.call(arguments, 1),
fToBind = this,
fNOP = function () {},
fBound = function () {
return fToBind.apply(this instanceof fNOP
? this
: oThis || window,
aArgs.concat(Array.prototype.slice.call(arguments)));
};
fNOP.prototype = this.prototype;
fBound.prototype = new fNOP();
return fBound;
};
}
scope of variables in JavaScript callback functions
You're sharing the single i
variable among all of the callbacks.
Because Javascript closures capture variables by reference, the callbacks will always use the current value of i
. Therefore, when jQuery calls the callbacks after the loop executes, i
will always be 2
.
You need to reference i
as the parameter to a separate function.
For example:
function sendRequest(i) {
$.get('http://www.google.com/', function() {
alert(i);
});
}
for (var i = 0; i < 2; i++) {
sendRequest(i);
}
This way, each callback will have a separate closure with a separate i
parameter.
Passing 'this' scope to callback function
Use bind then:
do_this((function() {
returh this.test + 5;
}.bind(this));
How to access the correct `this` inside a callback
What you should know about this
this
(aka "the context") is a special keyword inside each function and its value only depends on how the function was called, not how/when/where it was defined. It is not affected by lexical scopes like other variables (except for arrow functions, see below). Here are some examples:
function foo() {
console.log(this);
}
// normal function call
foo(); // `this` will refer to `window`
// as object method
var obj = {bar: foo};
obj.bar(); // `this` will refer to `obj`
// as constructor function
new foo(); // `this` will refer to an object that inherits from `foo.prototype`
To learn more about this
, have a look at the MDN documentation.
How to refer to the correct this
Use arrow functions
ECMAScript 6 introduced arrow functions, which can be thought of as lambda functions. They don't have their own this
binding. Instead, this
is looked up in scope just like a normal variable. That means you don't have to call .bind
. That's not the only special behavior they have, please refer to the MDN documentation for more information.
function MyConstructor(data, transport) {
this.data = data;
transport.on('data', () => alert(this.data));
}
Don't use this
You actually don't want to access this
in particular, but the object it refers to. That's why an easy solution is to simply create a new variable that also refers to that object. The variable can have any name, but common ones are self
and that
.
function MyConstructor(data, transport) {
this.data = data;
var self = this;
transport.on('data', function() {
alert(self.data);
});
}
Since self
is a normal variable, it obeys lexical scope rules and is accessible inside the callback. This also has the advantage that you can access the this
value of the callback itself.
Explicitly set this
of the callback - part 1
It might look like you have no control over the value of this
because its value is set automatically, but that is actually not the case.
Every function has the method .bind
[docs], which returns a new function with this
bound to a value. The function has exactly the same behavior as the one you called .bind
on, only that this
was set by you. No matter how or when that function is called, this
will always refer to the passed value.
function MyConstructor(data, transport) {
this.data = data;
var boundFunction = (function() { // parenthesis are not necessary
alert(this.data); // but might improve readability
}).bind(this); // <- here we are calling `.bind()`
transport.on('data', boundFunction);
}
In this case, we are binding the callback's this
to the value of MyConstructor
's this
.
Note: When a binding context for jQuery, use jQuery.proxy
[docs] instead. The reason to do this is so that you don't need to store the reference to the function when unbinding an event callback. jQuery handles that internally.
Set this
of the callback - part 2
Some functions/methods which accept callbacks also accept a value to which the callback's this
should refer to. This is basically the same as binding it yourself, but the function/method does it for you. Array#map
[docs] is such a method. Its signature is:
array.map(callback[, thisArg])
The first argument is the callback and the second argument is the value this
should refer to. Here is a contrived example:
var arr = [1, 2, 3];
var obj = {multiplier: 42};
var new_arr = arr.map(function(v) {
return v * this.multiplier;
}, obj); // <- here we are passing `obj` as second argument
Note: Whether or not you can pass a value for this
is usually mentioned in the documentation of that function/method. For example, jQuery's $.ajax
method [docs] describes an option called context
:
This object will be made the context of all Ajax-related callbacks.
Common problem: Using object methods as callbacks/event handlers
Another common manifestation of this problem is when an object method is used as callback/event handler. Functions are first-class citizens in JavaScript and the term "method" is just a colloquial term for a function that is a value of an object property. But that function doesn't have a specific link to its "containing" object.
Consider the following example:
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = function() {
console.log(this.data);
};
The function this.method
is assigned as click event handler, but if the document.body
is clicked, the value logged will be undefined
, because inside the event handler, this
refers to the document.body
, not the instance of Foo
.
As already mentioned at the beginning, what this
refers to depends on how the function is called, not how it is defined.
If the code was like the following, it might be more obvious that the function doesn't have an implicit reference to the object:
function method() {
console.log(this.data);
}
function Foo() {
this.data = 42,
document.body.onclick = this.method;
}
Foo.prototype.method = method;
The solution is the same as mentioned above: If available, use .bind
to explicitly bind this
to a specific value
document.body.onclick = this.method.bind(this);
or explicitly call the function as a "method" of the object, by using an anonymous function as callback / event handler and assign the object (this
) to another variable:
var self = this;
document.body.onclick = function() {
self.method();
};
or use an arrow function:
document.body.onclick = () => this.method();
Callback function cannot access variable within parent function's scope
You could use a closure over text
for finding the element.
function removeText(text) { array.splice(array.findIndex(findTextToDelete(text)), 1);}
function findTextToDelete(text) { return function (element) { return element === text; }}
var array = ['a', 'b', 'removeThis', 'c'];
removeText("removeThis");console.log(array)
Javascript - scope is lost in callback
use bind:
this.callback = function(){
this.doSomethingUseful(); // doesn't work
}.bind(this);
Javascript, outside variable scope in callback function?
You're dealing with closures, rewrite your code as follows:
...
(function(id) {
User_UidSearch(results[id].employee, function (user) {
console.log(id);
// results[i]['email'] = user.email;
});
})(i);
I.e. wrap your function to unbind i
.
scope of variable inside an event listener callback function
Your issue is closures, or lexical scoping . You're seeing i
as the value of the last index because of lexical scoping. By the time the onclick happens, i
will always be the last index. You can inject i
in a closure, as such:
(function(_i) {
image.onclick = function () {
console.log(this.index); //gives the real index
console.log(_i); //gives the length of components+1
};
})(i);
see here for more information
Related Topics
Why Do We Need to Install Gulp Globally and Locally
What Is This Practice Called in JavaScript
Fix Node Position in D3 Force Directed Layout
Dynamically Arrange Some Elements Around a Circle
How to Merge Two JavaScript Objects Together in Es6+
Setting Multiple Attributes for an Element at Once with JavaScript
Using Async/Await Inside a React Functional Component
Getting the User Id from a Database Trigger in Cloud Functions for Firebase
Chrome Extension: Port Error: Could Not Establish Connection. Receiving End Does Not Exist
How to Print a Stack Trace in Node.Js
JavaScript - How to Get the Url of Script Being Called
How to Check If a Number Is Between Two Values
Using Multiple Mongodb Databases with Meteor.Js
How to Write Your Own Custom Legends for Google Line Chart/ Google Line Chart Legend Manipulation