Pass Correct "This" Context to Settimeout Callback

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 set this 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



Leave a reply



Submit