Pass correct this context to setTimeout callback?
EDIT: In summary, back in 2010 when this question was asked the most common way to solve this problem was to save a reference to the context where the setTimeout
function call is made, because setTimeout
executes the function with this
pointing to the global object:
var that = this;
if (this.options.destroyOnHide) {
setTimeout(function(){ that.tip.destroy() }, 1000);
}
In the ES5 spec, just released a year before that time, it introduced the bind
method, this wasn't suggested in the original answer because it wasn't yet widely supported and you needed polyfills to use it but now it's everywhere:
if (this.options.destroyOnHide) {
setTimeout(function(){ this.tip.destroy() }.bind(this), 1000);
}
The bind
function creates a new function with the this
value pre-filled.
Now in modern JS, this is exactly the problem arrow functions solve in ES6:
if (this.options.destroyOnHide) {
setTimeout(() => { this.tip.destroy() }, 1000);
}
Arrow functions do not have a this
value of its own, when you access it, you are accessing the this
value of the enclosing lexical scope.
HTML5 also standardized timers back in 2011, and you can pass now arguments to the callback function:
if (this.options.destroyOnHide) {
setTimeout(function(that){ that.tip.destroy() }, 1000, this);
}
See also:
- setTimeout - The 'this' problem
How can I pass a parameter to a setTimeout() callback?
setTimeout(function() {
postinsql(topicId);
}, 4000)
You need to feed an anonymous function as a parameter instead of a string, the latter method shouldn't even work per the ECMAScript specification but browsers are just lenient. This is the proper solution, don't ever rely on passing a string as a 'function' when using setTimeout()
or setInterval()
, it's slower because it has to be evaluated and it just isn't right.
UPDATE:
As Hobblin said in his comments to the question, now you can pass arguments to the function inside setTimeout using Function.prototype.bind()
.
Example:
setTimeout(postinsql.bind(null, topicId), 4000);
JS: How to pass context to setTimeOut anonymous function which accesses a function class
Thanks to those who gave their insight to my coding problem, I have worked out what I needed to fix in my code. My understanding of scope, this, and bind needed some adjusting.
To start with, my anonymous function needed to use "this".
t.setTimeoutTest(function() {this.holdTimers[i].timerStarted = false;}, 3000);
Which seemed misleading to me because "this" would have the wrong context, until I tried the following in the function class's setTimetOutTest function:
this.setTimeoutTest = function(runFunction, millisecondsDelay) {
var IDofTimer = setTimeout(runFunction.bind(this), millisecondsDelay);
}
If this were the end of it, I would have marked the above answer correct. But I wasn't so lucky.
Using this code would cause an error which crashed my app. I finally realized that my for loop was programmed for 7 iterations, but that the error was crashing on the 8th. It seems (in my un-expert opinion) that as soon as the 7th iteration finished, it then incremented the index to the 8th. This was a problem because I used the index to reference a variable, but by the time t.setTimeout() called the function the variable had already incremented to an undefined element in the array, causing my code to crash.
I resolved this by saving the index to a variable and using it in my for anonymous function.
var myI = i;
t.setTimeoutTest(function() {this.holdTimers[myI].timerStarted = false;}, 3000);
Now, my code works as expected and is more robust, although a little unclean. Thanks everyone!
when do setTimeOut callback function get its parameters?
Callback function of the setTimeout
isn't executed until the call stack is empty and the call stack won't be empty until the execution of your synchronous javascript code completes.
As the last statement overwrites the value of x
, when the callback function of setTimeout
is invoked, it logs the latest value, i.e. 7.
It seems that you are expecting the callback function of setTimeout
to be executed while the for
loop is executing BUT that's not how Javascript works.
Once the timer of the setTimeout
expires, callback function of the setTimeout
is enqueued in the task queue. From the task queue, event loop will push this callback on the call stack BUT the callback function won't be pushed on the call stack until no other javascript code is executing, i.e. the call stack is empty.
It doesn't matters how long it takes for the synchronous script execution to end; no scheduled callback will be executed until the synchronous script execution ends.
Keep in mind that all your code executes on a single thread.
Also note that the number of milliseconds passed to setTimeout
is NOT the exact time after which the callback function will be executed; it's the minimum time after which the callback function will be invoked.
setTimeout callback argument
Your question really has nothing at all to do with setTimeout
. You simply need to understand the difference between a function call and a reference to a function.
Consider these four assignments:
var one = function() { mike.showName(); };
var two = mike.showName;
var three = mike.showName();
var four = (function() { mike.showName(); })();
The first two assign a reference to a function to their respective variables. The last two, however, call functions (that's what the parens are for) and assign their return values to the vars on the left-hand side.
How this relates to setTimeout:
The setTimeout
function expects as its first argument a reference to a function, so either one
or two
above would be correct, but three
and four
would not. However, it is important to note that it is not, strictly speaking, a mistake to pass the return value of a function to setTimeout
, although you'll frequently see that said.
This is perfectly fine, for example:
function makeTimeoutFunc(param) {
return function() {
// does something with param
}
}
setTimeout(makeTimeoutFunc(), 5000);
It has nothing to do with how setTimeout
receives a function as its argument, but that it does.
What is this when in the callback of setTimeout?
setTimeout
is generally defined as window.setTimeout
in browsers, and can be called as just setTimeout
because it's available in the global scope.
That also means the context, and this
value, is always window
, unless another this
value is explicitly set.
MDN says
Code executed by
setTimeout()
is called from an execution context
separate from the function from which setTimeout was called.The usual rules for setting the
this
keyword for the called function
apply, and if you have not setthis
in the call or with bind, it
will default to the global (or window) object in non–strict mode, or
be undefined in strict mode.It will not be the same as the
this
value for the function that
called setTimeout.
MDN also outlines a number of ways to solve the "this-problem" in setTimeout
.
Personally I think I would just take the easy way out, and use a variable
Greeter.prototype.delayed_greet = function() {
var that = this;
setTimeout( function cb() {
console.log(' Hello ' + that.name);
}, 500);
};
Another option would be an arrow function, as they keep the surrounding context and don't create their own context.
var o = {
fn () {
setTimeout( () => { console.log(this) }, 500)
}
}
var o2 = {
fn () {
setTimeout( function() {
console.log(this === window)
}, 1000)
}
}
o.fn(); // fn() --- arrow function
o2.fn(); // true, is window --- regular function
SetTimeout() call without callback function behaves in unexpected manner
The number that you see in the console is the ID
returned by the setTimeout()
call, which you can use later to clear the time out i.e. cancel it.
For example:
const id = setTimeout(console.log, 1000, "this is cancelled");
clearTimeout(id); //cancelling the earlier timeout
setTimeout(console.log, 1000, "this is not cancelled");
function call within setTimeout not executing
That is because these, this inside the callback, does not refer to the object outside.
Try this:
delayFilter() {
let timeout = null;
clearTimeout(timeout);
let self = this;
timeout = setTimeout(function() {
self.filterProducts();
}, 1000);
}
filterProducts() {
//do stuff
}
You can also try the arrow function. The reason can be seen here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Functions/Arrow_functions
An arrow function expression is a syntactically compact alternative to a regular function expression, although without its own bindings to the this, arguments, super, or new.target keywords.
delayFilter() {
let timeout = null;
clearTimeout(timeout);
timeout = setTimeout(() => {
this.filterProducts();
}, 1000);
}
filterProducts() {
//do stuff
}
Related Topics
Window.Close and Self.Close Do Not Close the Window in Chrome
Is It an Anti-Pattern to Use Async/Await Inside of a New Promise() Constructor
_Proto_ Vs. Prototype in JavaScript
Deleting Array Elements in JavaScript - Delete VS Splice
How to Append Something to an Array
Pretty-Print Json Using JavaScript
Selecting Text in an Element (Akin to Highlighting With Your Mouse)
How Does Data Binding Work in Angularjs
How to Detect Page Zoom Level in All Modern Browsers
Cloud Functions For Firebase Trigger on Time
How to Replace a Character At a Particular Index in JavaScript
JavaScript or (||) Variable Assignment Explanation
How to Update Nested State Properties in React
Benefits of Using 'Object.Create' For Inheritance
Loop Through an Array in JavaScript
How to Initialize a JavaScript Date to a Particular Time Zone