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
setTimeout() inside JavaScript Class using this
You can do this:
var that = this;
setTimeout(function () {
that.doStuff();
}, 4000);
You can also bind
for more succinct code (as originally pointed out by @Raynos):
setTimeout(this.doStuff.bind(this), 4000);
bind
is a standard library function for exactly this coding pattern (ie capturing this
lexically).
setTimeout and this in JavaScript
The issue is that setTimeout()
causes javascript to use the global scope. Essentially, you're calling the method()
class, but not from this
. Instead you're just telling setTimeout
to use the function method
, with no particular scope.
To fix this you can wrap the function call in another function call that references the correct variables. It will look something like this:
test.protoype.method = function()
{
var that = this;
//method2 returns image based on the id passed
this.method2('useSomeElement').src = "http://www.some.url";
var callMethod = function()
{
that.method();
}
timeDelay = window.setTimeout(callMethod, 5000);
};
that
can be this
because callMethod()
is within method's scope.
This problem becomes more complex when you need to pass parameters to the setTimeout
method, as IE doesn't support more than two parameters to setTimeout
. In that case you'll need to read up on closures.
Also, as a sidenote, you're setting yourself up for an infinite loop, since method()
always calls method()
.
setTimeout in JS
The main confusion you are having is pretty common and comes from the fact that you are using a loop. Everything outside of the timer callback is JavaScript that is being executed synchronously with no delay. The loop executes 5 times immediately when you run the code and so 5 instances of the timer callback function get placed on the event queue in a matter of milliseconds and it is from that point in time that all 5 timer callbacks are delayed, rather than one callback being delayed from the completion of the prior one.
The first instance of the callback then runs after its initial delay is reached (1 second), then the second instance runs after its delay (2 seconds, which is only 1 second later than when the first function call ran) and then the third one runs (which is only 1 second behind the previous one of 2 seconds) and so on.
What you need to do is place the first instance of the callback on the event queue with a 1 second delay before it runs and then, when that first instance has completed, place another instance on the event queue with a 2 second delay, and so on.
To do this, forget the loop and make the timer function recursive, which will essentially cause a repeating code call, just as a loop would.
let delay = 1000;
let timer = null; // Will hold a reference to the timer
function x() {
timer = setTimeout(function(){
console.log(delay / 1000);
delay += 1000;
if(delay > 5000){
clearTimeout(timer); // Cancel the timer
console.log("Operation Complete!");
} else {
// Because the next call for the parent function comes from within
// the timer callback function, it is delayed until the end of that
// callback function's execution.
x();
}
}, delay);
}
x();
setTimeout executes after for loop in javascript
JS is sync. So all sync code is done first, and all async go in separate thread and they may finish earlier but they have to wait until all sync code is done.
setTimeout(function(){
console.log('setTimeout executes');
},1000); // this function go async in separate thread
for(var i=0;i<10000;i++){
console.log('inside for loop'); // sync
}
console.log('after For Loop'); // sync
// after all sync code async result will be called
// console.log('setTimeout executes'); will happen here
If you want full picture of how JS engines works read this. It is very basic and helps a lot.
how to stop the setTimeout function in javascript in this case?
try this
var myVar = setTimeout(function, milliseconds);
clearTimeout(myVar);
calling this._super() from inside setTimeout()
To make this work you need to capture the _super method while in the subclassed method, like this:
dance: function(){
// capture the super method for later usage
var superMethod = this._super;
window.setTimeout(function(){
return superMethod();
},50);
};
The reason this works, and your code does not, is that the extend()
method in inherit.js captures the superclass'
method as this._super
right before your overridden method is run. It then runs your code, and after your code has
run it restores _super to whatever it was set to before it ran. The action takes place in the following bit of code from inherit.js
var tmp = this._super;
// Add a new ._super() method that is the same method
// but on the super-class
this._super = _super[name];
// The method only need to be bound temporarily, so we
// remove it when we're done executing
var ret = fn.apply(this, arguments);
this._super = tmp;
return ret;
So to be more specific; when the original code was run, the function that was used as a parameter to setTimeout was bound to the original object. The reason it did not work was that, even though this
referenced the right object, this._super
referenced something else, since this._super
was reset to point to whatever it pointed to before the method was run. Probably it was not set, so the value of this._super
was most likely just undefined
.
why can't we do call and apply on setTimeout?
From the WHATWG setTimeout documentation:
The setTimeout() method must return the value returned by the timer initialisation steps, passing them the method's arguments, the object on which the method for which the algorithm is running is implemented (a Window or WorkerGlobalScope object) as the method context, and the repeat flag set to false.
setTimeout
needs to be called from the context of the window
object. The context passed into the .call
method is not the window object. To call setTimeout
correctly, do:
setTimeout.call(window, callback, delay);
setTimeout function not working with for loop
I got it working with this:
(Thanks Yotam Salmon for your answer, thats what helped me understand what I was doing wrong)
I basically got each function to set the timeout and run the following one, then i got the last one to run the first one again. all i needed then was to start the first one manually then it just ran forever.
function slide1to2(){ document.getElementById('slide1').className = "hidden"; setTimeout(slide2to3, 4000);}function slide2to3(){ document.getElementById('slide2').className = "hidden"; setTimeout(slide3to1, 4000);}function slide3to1(){ document.getElementById('slide1').className = ""; document.getElementById('slide2').className = ""; setTimeout(slide1to2, 4000);}
slide1to2();
div#slideshow{ width:100%; height:50%; background-image:url("white-wall.png");}
div#slideshow div{ width:100%; height:inherit; background-repeat:no-repeat; background-position:center; background-color:rgba(0,0,0,0.5); position:absolute; transition:1s;}
div#slideshow div[id$="1"]{ background:#FF8888; transform:translateX(0%); z-index:3;}
div#slideshow div[id$="2"]{ background:#88FF88; transform:translateX(0%); z-index:2;}
div#slideshow div[id$="3"]{ background:#8888FF; transform:translateX(0%); z-index:1;}
div#slideshow div[id$="1"].hidden{ transform:translateX(-100%);}
div#slideshow div[id$="2"].hidden{ transform:translateX(-100%);}
<div id="slideshow"> <div id="slide1"></div> <div id="slide2"></div> <div id="slide3"></div></div>
JavaScript do something during the settimeout
Just place the code immediately after the call to setTimeout()
:
setTimeout(function(){
// Code to be executed after timeout goes here
}, 10000);
// Code to be executed immediately goes here
Related Topics
Running JavaScript in Selenium Using Python
Why Can't I Add Properties to a String Object in JavaScript
How to Get Jquery to Select Elements with a . (Period) in Their Id
Sending Emails with JavaScript
Axios Posting Params Not Read by $_Post
How to Disable an <Option> in a <Select> Based on Its Value in JavaScript
Loading an Image to a <Img> from <Input File>
How to Ensure a <Select> Form Field Is Submitted When It Is Disabled
Jquery: How to Change Tag Name
Formdata.Append("Key", "Value") Is Not Working
Addeventlistener Not Working in IE8
Url Hash-Bang (#!/) Prefix Instead of Simple Hash (#/) in Angular 1.6
Jquery Ajax Call to PHP Script with JSON Return