How access object which created interval from interval callback? - javascript

I am new to JavaScript and coming not forth with my current issue.
I have here an React App and I try to expand it by a timer functionality. I created a timer (similar to this question: setInterval in a React app) and the timer functionality itself is working.
But the timer callback (or more exactly the setInterval callback) is not working. In the setInterval the variable this is pointing to Windows but I thought it should point to App, which is the class/object who created the timer and contains the setInterval callback. Therefore I get following error.
Here are the crucial code changes I made:
class App extends Component {
//...
componentDidMount() {
//...
var intervalId = setInterval(this.timer, 5000);
// store intervalId in the state so it can be accessed later:
this.setState({intervalId: intervalId});
}
componentWillUnmount() {
//...
// use intervalId from the state to clear the interval
clearInterval(this.state.intervalId);
}
timer() {
for (let i = 0; i < this.state.whitelist.length; i++) {
this.requestDeviceStatus(this.state.whitelist[i]);
}
}
//...
}
But you can access the complete source code/commit here:
https://github.com/wewa00/Reacticz/blob/backgroundtimer/src/App.js,
(And this are the Relevant commits: 02e3e93, 6bcabba.)
Why this pointing to Window and how can I access App from within timer()?

TypeError
Use timer function with ES6 arrow method.
timer=()=>{
for (let i = 0; i < this.state.whitelist.length; i++) {
this.requestDeviceStatus(this.state.whitelist[i]);
}
}

You need to use bind to access App inside timer function.
class App extends Component {
//...
componentDidMount() {
//...
var intervalId = setInterval(this.timer, 5000);
// store intervalId in the state so it can be accessed later:
this.setState({intervalId: intervalId});
this.timer = this.timer.bind(this); // <------------------------------
}
componentWillUnmount() {
//...
// use intervalId from the state to clear the interval
clearInterval(this.state.intervalId);
}
timer() {
for (let i = 0; i < this.state.whitelist.length; i++) {
this.requestDeviceStatus(this.state.whitelist[i]);
}
}
//...
}

Related

JavaScript Scoping Can't Access setInterval

Hey guys can someone just quickly help me out here.
I have an interval for a slideshow in one function and I want to clear it from another function without using global scopes as I know it is bad practice.
Can someone kindly help here please?
function beginSlideshow() {
var interval = setInterval(function () {
//Slideshow content here
}
function revertSlideshow() {
clearInterval(interval);
}
You have to store the timer handle somewhere. :-)
You have lots of options:
Modules
You could use modules. Then a top-level declaration of interval wouldn't be a global, it would only be accessible to the module:
let interval = 0;
export function beginSlideshow() {
interval = setInterval(function () {
//Slideshow content here
}, someValue);
}
export function revertSlideshow() {
clearInterval(interval);
interval = 0;
}
In a closure's scope
Similar concept to the module above, but without using modules:
const { beginSlideshow, revertSlideshow } = (() => {
let interval = 0;
function beginSlideshow() {
interval = setInterval(function () {
//Slideshow content here
}, someValue);
}
function revertSlideshow() {
clearInterval(interval);
interval = 0;
}
return { beginSlideshow, revertSlideshow };
})());
In the caller's scope
You could make this the problem of the person calling beginSlideshow by returning the function to stop it:
function beginSlideshow() {
const interval = setInterval(function () {
//Slideshow content here
}, someValue);
return () => {
clearInterval(interval);
};
}
The caller would use that like this:
const revertSlideshow = beginSlideShow();
// ...
revertSlideshow();
Another way to store it in the caller's scope is to wrap this up in a class and have the handle be a data property:
class Slideshow {
interval = 0;
begin() {
this.interval = setInterval(/*...*/);
}
revert() { // I'd call it "end"
clearInterval(this.interval);
this.interval = 0;
}
}

Calling class methods inside javascript class

This is a Vue class. The method signOut() should fire when the timer ticks. The timer works, except the call signOut().
The problem is with accessing the class method. I'm confused with this, self and access modifiers.
I tried with this.signOut() but it does not work.
How can I call the method signOut?
"use strict";
(async (globals, config, loader, application) => {
const storageLocal = await loader.services.storage.local.getAsync();
class HeaderComponent {
#foo = a;
constructor(tag) {
this.tag = tag;
this.timer();
}
signOut() {
storageLocal.delete('account');
window.location = '/signin.html';
}
timer() {
//document.getElementById("timer"),
var counter = -1;
var timeout;
var startTimer = function timer() {
counter++;
console.log(counter);
signOut(); //<- error can't call class method
timeout = setTimeout(timer, 10000);
};
function resetTimer() {
// here you reset the timer...
clearTimeout(timeout);
counter = -1;
startTimer();
//... and also you could start again some other action
}
document.addEventListener("mousemove", resetTimer);
document.addEventListener("keypress", resetTimer);
startTimer();
}
data() {
return { account: storageLocal.account };
}
}
const component = new HeaderComponent('component-header')
loader.components.set(component.tag, component);
})(window, window.config, window.loader, window.application);
Please note:
signOut() {
storageLocal.delete('account');
window.location = '/signin.html';
}
timer() {
//document.getElementById("timer"),
var counter = -1;
var timeout;
var startTimer = function timer() {
as you can see 'signOut()' is 2 levels below active functions. The logic says it would work like this.parent.signOut() but it DOES NOT !
EDIT3: this.signOut(); will produce
Uncaught TypeError: Cannot read property 'signOut' of undefined
at timer (header.js:30)
at HTMLDocument.resetTimer
The function creates a new context. You need to switch to arrow function and use this.signOut(). Simplified example:
timer() {
var counter = -1;
var timeout;
var startTimer = () => {
counter++;
console.log(counter);
this.signOut();
timeout = setTimeout(startTimer, 1000);
};
setTimeout(startTimer, 1000);
}
Moreover, you have two signOut() methods defined in one class.
You need this and call it like this.signOut()
The startTimer-function does not run in the context of the HeaderComponent's instance.
this in startTimer will point to window when it's executed as a handler in setTimeout.
In order to access the the instance of HeaderComponent, either use an arrow function (as pointed out in an earlier answer. See also Arrow function expressions) which will point this to the outer context (which is HeaderComponent's instance) or define an identifier in timer which points to the instance (eg. const self = this;) and use self instead of this in startTimer.
To apply this to your example (for the sake of consistency, I used var instead of const):
timer() {
var counter = -1;
var timeout;
var self = this;
var startTimer = function() { // Don't use a named function here, it only leads to more confusion
counter++;
console.log(counter);
self.signOut(); // Use `this` of the outer context
timeout = setTimeout(startTimer, 10000); // Use the declared identifier
};
// Rest of the method
}
this is Javascript may be a bit confusing to those who come from different programming languages. If you want to get into more detail, I recommend reading into the MDN reference for this and into Closures

Bind class to an instance of another class

I'm building an JS application where I'm using multiple timers (digital, analog). I would like to use a base class for the Timer with the functions: start, stop, update, etc.
Every time there is a timer created there are also new onChange event created. So when the timer ticks multiple instances get an update, not only the one where the timer is created in.
My question is: how can I bind and Timer instance the another class?
Timer class:
class Timer = {
constructor() {
this.seconds = 0;
}
start() {
this.timer = setInterval(update, 25);
}
stop() {
clearInterval(this.timer);
}
update() {
this.seconds += 1;
//emit data
let event = new Event("timer-tick");
event.detail = {
seconds: seconds,
}
document.body.dispatchEvent(event);
}
}
DigitalTimer class:
class DigitalTimer = {
constructor() {
this.timer = new Timer();
this.handleEvent();
}
handleEvent() {
$('body').on('timer-tick', function(e) {
//tick, do somehting with it.
});
}
start() {
this.timer.start();
}
stop() {
this.timer.stop()
}
}
There is a bind method on the Function prototype that does what you want.
start() {
this.timer = setInterval(this.update.bind(this), 25);
}
On a side note, you shouldn't rely on setInterval or setTimeout to increment the time. Of course they are useful to make periodic calls, but the elapsed time isn't guaranteed. You can instead compare an initial Date object with a new one on each call.
I did get it working by binding an on and trigger event on a plain object.
http://api.jquery.com/jQuery/#working-with-plain-objects
Working sample:
https://jsfiddle.net/q5s6cud3/
class Timer {
constructor() {
let self = this;
this.timer = setInterval(function() {
self.update();
}, 1000);
}
update() {
$(this).trigger('timer-tick');
}
}
class DigitalTimer {
constructor() {
this.timer = new Timer();
$(this.timer).on('timer-tick', function() {
console.log('yes');
});
}
}
const digitalTImer = new DigitalTimer();

Vue.JS countdown not works

I have a vue application, but the countdown not work good.
Actually i dont know why.
View {{ $parent.timer }} i see the good value.
Vue data:
data: function() {
return {
timer : 3,
...
and here is my countdown function:
countdown : function(time,callback)
{
//time is equal 5
this.timer = time;
//this.timer is equal 5
var timerInterval = undefined;
timerInterval = setInterval(function() {
if(this.timer == 1) {
this.timer = 0;
callback();
return clearInterval(timerInterval);
}
// First time undefined, in 2nd is NaN
this.timer -= 1;
// NaN
}, 1000);
}
call function:
this.countdown(data.time,function(){ //smtng });
What i do bad? Its work in my older Vue application.
I hope someone can help to me :)
Thanks so much!
It is an issue with scope of this, as explained below:
function() {...} creates a new scope inside. If you use this inside this function, it does not refer to outer scope. Therefore your this.timer of Vue component does not get updated from inside your setInterval().
() => {...} works like a function but does not create a new scope inside.
Check if the following code works:
timerInterval = setInterval(() => {
if(this.timer == 1) {
this.timer = 0; // `this` points to the scope of Vue component
callback();
// ...
}
// ...
}, 1000);
More info on arrow functions: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Functions/Arrow_functions

Why calling of window.setTimeout for the same function doesn't introduce infinite loop?

I saw the following code:
class MasterControlPanel {
private sensors: Sensor[] = [];
constructor() {
// Instantiating the delegate HeatSensor
this.sensors.push(new HeatSensor(this));
}
start() {
for (var i= 0; i < this.sensors.length; i++) {
// Calling the delegate
this.sensors[i].check();
}
window.setTimeout(() => this.start(), 1000);
}
startAlarm(message: string) {
console.log('Alarm! ' + message);
}
}
var cp = new MasterControlPanel();
cp.start();
Why window.setTimeout(() => this.start(), 1000); doesn't introduce infinite loop?
Based on my understanding, cp.start() will first iterate each sensor inside sensors and then call the window.setTimeout which in turn calls the start again after 1 second delay.
Reference: Listing 3-3. Delegation in Pro TypeScript: Application-Scale JavaScript Development
In short, it doesn't introduce an infinite loop because setTimeout doesn't block. Instead, the function you give it to execute, () => this.start() is added to a queue and executed after 1 second of time passes. The start method returns after calling window.setTimeout, and thus doesn't cause an infinite loop in the imperative sense. Most of the time, the code will be in a state of waiting until the next timer comes up.
That the code infinitely schedules a timer to call the start method is also true, but it isn't an infinite loop because it returns control to the javascript runtime.
It does introduce an infinite loop, delayed 1 sec between each call
window.setTimeout(() => this.start(), 1000);
is almost the same as
var _this = this;
window.setTimeout(function(){ _this.start(); }, 1000);
it's called Arrow Function
Maybe you need setInterval.
That one would create a infinite loop which you can clear by using clearInterval.
this.interval = window.setInterval(() => {
...
}, 5000);
window.clearInterval(this.interval);
Also you can do
this.sensors.forEach(sensor => sensor.check())
Instead of using for loop.
You can't use this in the setTimeout, because it refers to the setTimeout, not to your class.
Try this :
class MasterControlPanel {
private sensors: Sensor[] = [];
constructor() {
// Instantiating the delegate HeatSensor
this.sensors.push(new HeatSensor(this));
}
var that = this;
start() {
for (var i= 0; i < this.sensors.length; i++) {
// Calling the delegate
this.sensors[i].check();
}
window.setTimeout(() => that.start(), 1000);
}
startAlarm(message: string) {
console.log('Alarm! ' + message);
}
}
var cp = new MasterControlPanel();
cp.start();

Categories

Resources