I'm writing a JavaSCript class that has a method that recursively calls itself.
Scheduler.prototype.updateTimer = function () {
document.write( this._currentTime );
this._currentTime -= 1000;
// recursively calls itself
this._updateUITimerHandler = window.setTimeout( arguments.callee , 1000 );
}
Property description:
_currentTime: the currentTime of the timer in miliseconds.
_updateUITimerHandler: stores the reference so can be used later with clearTimeout().
my problem is where I'm using recursion with setTimeout(). I know setTimeout() will accept some string to execute, or a reference to a function. since this function is method of an object, I don't know how to call it from outside. so I used the second format of setTimeout() and passed in a reference to the method itself. but it does not work.
Try this:-
Scheduler.prototype.startTimer = function() {
var self = this;
function updateTimer() {
this._currentTime -= 1000;
self.hTimer = window.setTimeout(updateTimer, 1000)
self.tick()
}
this.hTimer = window.setTimeout(updateTimer, 1000)
}
Scheduler.prototype.stopTimer = function() {
if (this.hTimer != null) window.clearTimeout(this.hTimer)
this.hTimer = null;
}
Scheduler.prototype.tick = function() {
//Do stuff on timer update
}
Well the first thing to say is that if you're calling setTimeout but not changing the interval, you should be using setInterval.
edit (update from comment): you can keep a reference from the closure if used as a class and setInterval/clearInterval don't require re-referencing.
edit2: it's been pointed out that you wrote callee which will work quite correctly and 100% unambiguously.
Out of completeness, this works:
function f()
{
alert('foo');
window.setTimeout(arguments.callee,5000);
}
f();
so I tried out document.write instead of alert and that is what appears to be the problem. doc.write is fraught with problems like this because of opening and closing the DOM for writing, so perhaps what you needed is to change the innerHTML of your target rather than doc.write
You could hold a pointer towards it...
/* ... */
var func = arguments.callee;
this._updateUITimerHandler = window.setTimeout(function() { func(); }, 1000);
/* ... */
Related
My draw() function stops executing after calling this.circle(). If I call the draw() method directly, it works perfectly. However, if I use setInterval(this.draw, 1000), it seems to return right when calling this.circle(). circle() is never executed either. Am I misusing setInterval?
function Ball() {
this.start = function() {
return setInterval(this.draw, 1000);
}
this.circle = function() {
console.log('1');
}
this.draw = function() {
console.log('2');
this.circle();
console.log('3');
}
this.circle() is never executed, and console.long('3') (or anything after it) is never reached.
The result is console.long('2') being repeatedly printed every 1 second
When you use setInterval, this will refer to window. window doesn't have a function called draw, so it will throw an error and stop execution. To fix it, you need to bind the value of this.
return setInterval(this.draw.bind(this), 1000);
“this” is no longer what you think it is, and you have lost context to it, it no longer represents Ball like you’d like it to. You can bind your draw function like “this.draw.bind(this)” or you can capture “this” using a line like “const _this = this;” and then only reference “_this” in your functions to make sure you are always accessing the correct one.
Some reading on the closures
https://javascript.info/closure
As stated by #iagowp this will refer to the window when your method is called by setInterval. Instead, you can fix this by using an ES6 arrow function to call this.draw(). This way your this is referring to the Ball object rather than the function which called this.draw().
See working example below:
function Ball() {
this.start = function() {
return setInterval(_ => this.draw(), 1000);
}
this.circle = function() {
console.log('1');
}
this.draw = function() {
console.log('2');
this.circle();
console.log('3');
}
}
let b = new Ball();
b.start();
I recently started learning javascript to help maintain some stuff and ran into this issue today:
this.moveChar = function(){
// body here
setTimeout(moveChar,1000);
}
this.initialise= function(){
this.moveChar();
}
When initialise is called, I expected moveChar to be called, then repeated call itself once every 1000ms
However, what actually happens is moveChar gets called once then that's it. Based on other stackoverflow posts I read, I suspected it might be something to do with the function being expressed rather than declared. I have tried to use
this.moveChar = function recMove(){
// body here
setTimeout(recMove,1000);
}
without luck either.
Any suggestions on how I can fix this?
EDIT: Main thing I need to do is have the moveChar function called once every second. If there is a better approach than setTimeout recursion, I'm open to it
this.moveChar is not the same as moveChar, unless this is the global scope object like window.
this.moveChar is a property on an object, while moveChar would reference any variable in a visible scope chain.
You can change it to a couple of things in order to keep scope of whatever object is being used:
Using an arrow function
this.moveChar = function(){
// body here
setTimeout(()=>this.moveChar(),1000);
}
Using .bind()
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this),1000);
}
You might want to consider using setInterval() which is the more appropriate API for this task.
What setInterval() does is - it will repeatedly call the given function upon a certain interval is reached.
See:
https://developer.mozilla.org/en-US/docs/Web/API/WindowTimers/setInterval
Quote:
Repeatedly calls a function or executes a code snippet, with a fixed
time delay between each call. Returns an intervalID.
Example:
Assuming moveChar() contains your operation logic. Then to repeat it you'll do this 1 line.
let moveChar = function(){
// Do stuff
console.log("Hi thanks for calling me!");
}
setInterval(moveChar, 1000);
Are you using this in side body here?
If so, you should bind correct context while call.
this.moveChar = function(){
// body here
setTimeout(this.moveChar.bind(this), 1000);
}
Or use anonymous function:
this.moveChar = function(){
// body here
var that = this;
setTimeout(function(){
that.moveChar();
}, 1000);
}
Or arrow function:
this.moveChar = function(){
// body here
setTimeout(() => this.moveChar(), 1000);
}
Same notes apply to setInterval variant:
this.initialise= function(){
setInterval(this.moveChar.bind(this), 1000);
// var that = this;
// setInterval(function(){that.moveChar();}, 1000);
// setInterval(() => this.moveChar(), 1000);
}
this.moveChar = function(){
// body here
alert('called moveChar');
}
this.initialise= function(){
setInterval(function(){moveChar();},1000);
}
this.initialise();//call here
I know that this within setTimeout will correspond by default to window, so I have been using bind and passing it through to the self executing function, but when setTimeout finally runs (it's fine on the first run, called by TeaBot._manageTeaRound(), but it's not fine when self executing) this is undefined. Here's my code, where am I going wrong? (I have deleted some lines of code which might not be relevant). Thanks for your help :)
TeaBot.prototype._manageTeaRound = function(originalMessage, channel){
var self = this;
self.teaMaker = this._getUserById(originalMessage.user);
//now wait 3 minutes for people to send their order
self._runTimer(self,channel);
}
TeaBot.prototype._runTimer =function(self, channel) {
// do stuff
console.log(self.teaMaker.name); //undefined
var interval = self.interval,
teaMaker = self.teaMaker;
console.log("self.interval is " + self.interval);
if(interval === 0){
interval++;
self.interval = interval;
setTimeout(self._runTimer.bind(self, channel), 180000);
}else{
self.interval = 0;
}
}
This line is problematic:
setTimeout(self._runTimer.bind(self, channel), 180000);
Function TeaBot.prototype._runTimer expects self to be the first param - Function.prototype.bind() first param is context (function's this). Try use it like this:
setTimeout(self._runTimer.bind(self, self, channel), 180000);
Or leave context empty, becouse you are not using this at all:
setTimeout(self._runTimer.bind(undefined, self, channel), 180000);
This is because you are not using the value of this inside of _runTimer. You are binding a value this, but _runTimer couldn't care less.
_runTimer cares about its two parameters. The 1st being the context (self). The way your code is structured, there's no reason to use .bind here.
setTimeout(function(){
self._runTimer(self, channel);
}, 180000);
(Since you are passing the context (self) to the function, it makes no sense to have _runTimer be a part of TeaBot.prototype, since it needs a context passed to it.)
As an alternative, you can drop the self parameter, and have _runTimer reference this. That's when you would need to use .bind().
TeaBot.prototype._manageTeaRound = function(originalMessage, channel){
this.teaMaker = this._getUserById(originalMessage.user);
//now wait 3 minutes for people to send their order
this._runTimer(channel);
};
TeaBot.prototype._runTimer = function(channel) {
// do stuff
console.log(this.teaMaker.name);
var interval = this.interval,
teaMaker = this.teaMaker;
console.log("this.interval is " + this.interval);
if(interval === 0){
interval++;
this.interval = interval;
setTimeout(this._runTimer.bind(this, channel), 180000);
} else{
this.interval = 0;
}
};
I have the following code:
function fn($){
return function(){
innerFn = function(){
setTimeout(show, 1000);
};
show = function(){
$.alert("TEST");
}
}
}
But, after one second, when the function show is run, it says $ is undefined. How do I resolve this issue?
how to pass arguments to a function in setTimeout
setTimeout has a built in mechanism for adding params
var timeoutID = window.setTimeout(func, delay, [param1, param2, ...]);
use it.
If you're going to use this - you should be careful. but that's another question.
There are a number of things at play here. The most important being that your setTimeout never gets called, since innerFn never gets called. This should do the trick.
function fn($){
return function(){
setTimeout(function(){
$.alert("TEST");
}, 1000);
}
}
fn(window)(); //triggers your alert after 1000ms
Your code makes no any sense, because nothing is called:
function fn($){
return function(){
innerFn = function(){
setTimeout(show, 1000);
};
show = function(){
$.alert("TEST");
}
}
}
Let's say I'm calling fn passing window, then a function is returned, that I can executed. But because this function is containing only function declaration - you also forget var so you pollute the global scope, that is bad - nothing is happen.
You'll need at least one function call inside, like:
function fn($){
return function(){
var innerFn = function(){
setTimeout(show, 1000);
};
var show = function(){
$.alert("TEST");
}
innerFn();
}
}
fn(window)();
And that will works. However, it's definitely redundant. You can just have:
function fn($){
return function(){
function show(){
$.alert("TEST");
}
setTimeout(show, 1000);
}
}
To obtain the same result. However, if you're goal is just bound an argument to setTimeout, you can use bind. You could use the 3rd parameter of setTimeout as the documentation says, but it seems not supported in IE for legacy reason.
So, an example with bind will looks like:
function show() {
this.alert('test');
}
setTimeout(show.bind(window), 1000);
Notice also that window is the global object by default, so usually you do not have to do that, just alert is enough. However, I suppose this is not your actual code, but just a mere test, as the alert's string says.
If you prefer having window as first parameter instead, and you're not interested in the context object this, you can do something like:
function show($) {
$.alert('test');
}
setTimeout(show.bind(null, window), 1000);
I'm currently wondering if there is a better solution than passing this scope to the lambda-function via the parameter 'e' and then passing it to 'funkyFunction' using call()-method
setInterval(function(e){e.funkyFunction.call(e)}, speed, this)
(Minor question aside: I'd been reading something about memory-leaks in javascript. How does the lambda-function affect my memory? Is it better to define it first like var i = function(e)... and then passing it as a parameter to setInterval?)
My situation may have been a bit different, but here's what I did:
var self = this;
setInterval(function() { self.func() }, 50);
My scenario was that my code was inside a class method and I needed to keep correct scope as I didn't want the 'this' binding to resolve to the current window.
eg. I wanted to run MyClass.animate from MyClass.init using setInterval so I put this scope-keep code into MyClass.init
You can use native bind function.
function Loop() {
this.name = 'some name for test';
setInterval( (function(){//wrap the function as object
//after bind, "this" is loop refference
console.log(this);
}).bind(this), 1000 );// bind the object to this (this is Loop refference)
}
var loop = new Loop();
paste this example in the console to see the result
What's wrong with simply relying on the outer-scope defined variable?
(function() {
var x = {};
setInterval(function() {
funkyFunction.call(x)
}, speed);
})();
I had the same question, but there seems to be no built in solution, so here is a quick workaround I punched together:
function setScopedInterval(func, millis, scope) {
return setInterval(function () {
func.apply(scope);
}, millis);
}
usage:
function MyClass() {
this.timer = null;
this.myFunc = function() { console.log('do some stuff'); };
this.run = function() {
this.timer = setScopedInterval(function () { this.myFunc(); }, 1000, this);
};
this.stop = function() { clearInterval(this.timer); };
}
var instance = new MyClass();
instance.run(); // will log to console every second
// until this line is called
instance.stop();
This only covers the use-case where you pass an actual function, not a string of code to be executed.
As for your question about memory leaks when using this functionality: it is not so much the problem with using setInterval as it is with anonymous functions in itself.
If you use a reference to an object inside a lambda, this reference will keep the referenced object in memory for as long as the anonymous function exists. I think the function is destroyed with a call to clearInterval.
I don't think there is any benefit from assigning the function to a variable first, on the contrary, it will create another variable containing a reference that will not be garbage collected as long as the anon func exists...
You may also have a look at the YUI Framework. It's fine for building applications and easy to learn.
YUI2: YAHOO.lang.later(when, scope, fn, args, periodic);
YUI3: Y.later(when, scope, fn, args, periodic);
UPDATE as example
Using YUI and jQuery (Do not forget enable $.noConflict())
var jQuerySelector = jQuery("div[class^='form-field-']");
jQuerySelector.hide();
jQuery(jQuerySelector[0]).show();
YAHOO.lang.later(5000, jQuery, function(jQuerySelector) {
if((!(this.index)) || (this.index == (jQuerySelector.length))) {
this.index = 0;
}
jQuerySelector.hide();
this(jQuerySelector[this.index++]).show();
}, jQuerySelector, true);
In short
1º parameter: 5000 on every 5000 miliseconds, 3º parameter (a function) will be executed
2º parameter: jQuery Object in which will be referenced by using this
3º parameter: function which will be executed. It receives as parameter either an array or an object passed as 4º parameter
5º parameter: true if true, executes continuously at supplied interval until canceled
see http://yuilibrary.com/yui/docs/api/classes/YUI.html#method_later
UPDATE
No need for $.noConflict() because YUI does not use $ in any way.
There are two important distinctions to make.
1) Do you want a reference to the passed parameter so that the timeout function can track changes made to it, or do you want a clone of the passed parameter?
2) Do you want to be able to capture a reference to the timeout in case you want to cancel it? (Yes!)
// Normal setTimeout: retains a reference to `test` and returns the bad value
var test = 'test: good';
var timer = setTimeout(function() { console.log(test); }, 1000);
test = 'test: bad';
// Test2 receives a clone of `test2` and returns the good value, but does so right away, not on a timeout
var test2 = 'test2: good';
var timer2 = setTimeout((function() { console.log(test2); })(test2), 1000);
test2 = 'test2: bad';
// Test3 receives a clone of `test3` and returns the good value, but doesn't return a reference to the timeout, and can't be canceled
var test3 = 'test3: good';
var timer3 = function(test3) { setTimeout(function() { console.log(test3); }, 1000); }(test3);
test3 = 'test3: bad';
// Test4 receives a clone of `test4` and returns the good value, and correctly returns timeout reference
var test4 = 'test4: good';
var timer4 = function(test4) { return setTimeout(function() { console.log(test4); }, 1000); }(test4);
test4 = 'test4: bad';
// Test5 retains a reference to `test5` and returns the bad value
var test5 = 'test5: good';
var timer5 = setTimeout((function() { console.log(test5); }).bind(test5), 1000);
test5 = 'test5: bad';
// Did we capture references to the timeouts?
console.log(typeof timer);
console.log(typeof timer2);
console.log(typeof timer3);
console.log(typeof timer4);
console.log(typeof timer5);