Getting a timer ID within its callback in setTimeout()? - javascript

I am using TypeScript and Angular to build an app. In a component, I create a few timeouts and I want to be sure to cancel them when my component is destroyed. To do so, I have a Set<number> that keeps track of active timeouts. When my component is destroyed, I simply iterate over this set and cancel all my timeouts. When I create a timeout, I add its ID to the Set. My problem is that I currently don't have a way to remove the ID from the Set when the timeout has executed. I want to be able to get the timeout ID inside my callback function to remove it from the Set. Is there a way to do this?
Here is some code that could explain:
export class CustomComponent implements OnDestroy {
private activeTimeouts: Set<number> = new Set<number>();
// Method called when component is destroyed
ngOnDestroy() {
this.activeTimeouts.forEach((item) => {
clearTimeout(item);
});
}
someFunction() {
const timerID = setTimeout(() => {
console.log("Hello World");
// How can I get timerID from here?
}, 1000);
this.activeTimeouts.add(timerID);
}
}
As you can see from my code, I would like to be able to remove the timerID after console.log("Hello World"); is executed. Is there any way to do this?

You can indeed use the timerID in the scope of setTimeout.
The function that's there within setTimeout is a closure.
Closure helps when functions are probably called later on in code but need to refer to variables outside their local scope when they're called.
const timerID = setTimeout(() => {
useTimerIdentifier(timerID);
console.log("timer accessed");
}, 3000);

Related

I was just start learn angular but I can't understand why we not work settimeout() outside the constructor in angular?

export class ServerComponent implements OnInit {
show = false;
setTimeout(() => {
this.show = true;
}, 2000);
constructor(){
}
ngOnInit(){
}
}
You cannot put setTimeout or anything that isn't a property of method inside a class definition.
Make sure you understand Classes properly!
The right way to do this would be to put the setTimeout inside your constructor function
Set timeout is basically a prebuilt library function (method) with two parameters.
callback function
timeout in milliseconds
so what you trying to do is calling a existing library function outside a method or constructor which is not allowed.

How to test a simple method that returns void in Cypress?

I have the following function that I'd like to test with Cypress:
let stopped = false; // global variable
export const = stopDoingStuff = () => {
stopped = true;
}
I want to test if the methods sets the variable stopped to true, after calling it.
This is my current state. I don't know how the check the global variable after calling the function...
it('should set stopped to true', async () => {
let stopped = false;
stopDoingStuff();
expect(stopped).to.equal(true);
});
It's always better to also mention what the result of your code is.
But I can see that variable stopDoingStuff is exported, so I assume the whole piece of code is in a separate module. Since stopped variable is not exported, you can't access it from anywhere but the module it's in.

How to use callbacks when specific variable is updated inside different functions

I'm aware that we can use useEffect to set callbacks to state changes involving specific variables, but when I was refactoring a class component to one that uses hooks I encountered a problem, I set the state of a variable in three different functions (using setState), and each time I call a different callback for these setStates.
If I declare a useEffect like this:
useEffect(() => {
callback1();
callback2();
callback3();
}, [myVariable]);
Each time myVariable changes I will call those 3 callback functions, which is not my intent, since they interfere with each other.
To clarify things, this is what I was doing in the class component:
function a () {
...
this.setState({myVariable: x}, () => do something that involves myVariable);
};
function b () {
...
this.setState({myVariable: y}, () => do another thing that involves myVariable);
};
function c () {
...
this.setState({myVariable: z}, () => do another thing that involves myVariable);
};
How to proceed and create a specif callback for each setMyVariable call?
Thanks a lot!
Why not call the correct callback function directly in the function where you're calling setMyVariable()?
use multiple useEffects
useEffect(() => {
callback1();
}, [myVariable1]);
useEffect(() => {
callback2();
}, [myVariable2]);

setInterval function without arrow function

I am learning about react components following the documentation https://facebook.github.io/react/docs/state-and-lifecycle.html
Why do we need to use arrow function here:
this.timerID = setInterval(() => this.tick(), 1000);
Why can't I just say (obviously it doesn't work)
this.timerID = setInterval(this.tick(), 1000);
The first argument for setInterval is of type function. If you write this:
this.timerID = setInterval(this.tick(), 1000);
…then you don't pass a function, instead you execute the function this.tick immediately and then pass the value returned by that function call as an argument.
You could write it like this:
this.timerID = setInterval(this.tick, 1000);
If you omit the parentheses, you pass a reference to your this.tick function, which is then executed by setInterval after 1000 milliseconds.
setInterval takes function as first argument, in the second case it is getting a returned value
Change it to
this.timerID = setInterval(this.tick.bind(this), 1000);
or
this.timerID = setInterval(() => this.tick(), 1000);
which is probably what you need when you want to bind the function to the React context.
See this answer on why you need to bind functions in React
If you don't you could have simply written
this.timerID = setInterval(this.tick, 1000);
Why do we need to use arrow function here
Answer is simple :
see the result inside live script example...
class Persons{
scopeOne(){
// your will see the result this will display our current object 'Persons'
setInterval(function(){
console.info(' SCOPE ONEo : '+this);
},3000);
}
scopeTwo(){
setInterval(function(){
console.info(' SCOPE TWO : '+this);
}.bind(this) ,3000);
}
scopeThree(){
setInterval(() => { console.info(' SCOPE THREE : '+this) },3000);
}
}
let p = new Persons();
p.scopeOne();
p.scopeTwo();
p.scopeThree();
in first scope the result is WINDOW OBJECT so we cannot access our current class scope
so in 2nd scope we using scope with bind(this) that helps to bind our current object scope,
and in third which do same as 2nd scope calling the current object....
The simple answer to that is that the tick function needs to be able access the context of its use/ this, which is the Clock component. In other words it needs to be bound to the Clock component so that it works in the context of the Clock component and not the global context which is everything outside of the Clock component.
Functions within React classes are not bound by default and failure to bind the function will return undefined instead of the expected result.
There are several ways to bind the tick function to the Clock component example from the Reactjs website.
Arrow functions can access this which is why they are used in the example. In other words, writing an arrow function implicitly means it binds its contents to the local context (the Clock component in this case). Read more on that in this Medium article
Bind the tick function in the constructor
class Clock extends React.Component{
constructor(props){
super(props);
this.state={date: new Date()}
this.tick=this.tick.bind(this)
}
Bind the tick function in the setInterval method
componentDidMount() {
this.timerID = setInterval(this.tick.bind(this),
1000
);
}
Transform a function called tick into a variable with an anonymous function, so you can pass it as an argument without parentheses.
Taking this code (https://codepen.io/gaearon/pen/amqdNr?editors=0010) as a base, you can update like this:
componentDidMount() {
this.timerID = setInterval(this.tick,1000);
}
tick = () => {
this.setState({
date: new Date()
});
}
Generally speaking, whenever you are going to pass a function as a parameter in js that uses an object variable, you need to BIND it with its object, so you don´t lose this reference. The two ways to do this are: using arrow functions or using bind().
You need to supply a function reference, you are trying to invoke a function, unless that function returns a function reference, your code will not work
It should look like so
this.tick = function() { .... }
this.timerID = setInterval(this.tick, 1000);
If you are not using arrow function then your code should look something like this:
this.timerID = setInterval(function(){ this.tick() }, 1000);
Technically, you should be able to use the second code snippet if this.tick() returns a function.

window.setInterval from inside an object

I'm currently having an issue where I have a javascript object that is trying to use setInterval to call a private function inside of itself. However, it can't find the object when I try to call it. I have a feeling that it's because window.setInterval is trying to call into the object from outside but doesn't have a reference to the object. FWIW - I can't get it to work with the function being public either.
The basic requirement is that I may need to have multiple instances of this object to track multiple uploads that are occurring at once. If you have a better design than the current one or can get the current one working then I'm all ears.
The following code is meant to continuously ping a web service to get the status of my file upload:
var FileUploader = function(uploadKey) {
var intervalId;
var UpdateProgress = function() {
$.get('someWebService', {},
function(json) {
alert('success');
});
};
return {
BeginTrackProgress: function() {
intervalId = window.setInterval('UpdateProgress()', 1500);
},
EndTrackProgress: function() {
clearInterval(intervalId);
}
};
};
This is how it is being called:
var fileUploader = new FileUploader('myFileKey');
fileUploader.BeginTrackProgress();
Use this
intervalId = window.setInterval(UpdateProgress, 1500);
setInterval with a literal argument will eval this in the global scope where UpdateProgress is not accessible.
Because it is an eval expression, it does not have access to the scope that setInterval is created in. Try:
intervalId = window.setInterval(UpdateProgress, 1500)
It is generally good practice to avoid eval style expressions wherever possible. For instance, if you wanted to call several functions from the same timer, you would use an anonymous function instead of a string.
window.setInterval(function () {
function1();
function2();
}, 1500)
See also
Why is using javascript eval() a bad idea?
Anonymous function - Wikipedia
+1 to Andy E's head (I can't upvote yet, doh!)
Another gotcha that could get you is if you use this from within the called function.
Then doing exactly what Andy has with this addition should get you by.
var that = this;
window.setInterval(function() {
function1.apply(that);
function2.apply(that);
}, 1500);

Categories