I want to be able to call setInterval (or something similar) at two different lengths, alternating.
For example, running a function after 5 seconds, then 1 second, then 5 seconds again, and so on.
Is this possible? I tried a function that alternates the value, but it didn't seem to work.
let num = 5000
function alternateNum() {
if (num === 5000) { num = 1000 }
else { num = 5000 }
}
setInterval(() => {
// ...
alternateNum()
}, num);
JS timers have a very complicated history.
Using a recursive setTimeout invocation is a simple and elegant solution as long (as your runtime implements tail call optimization).
Separate from the issue of recursion is the issue of timer drift. This is covered in the YouTube video JavaScript counters the hard way - HTTP 203 if you'd like an accessible introduction.
In many JS engines (e.g. V8) setInterval will handle drift correction for you, so there's actually an advantage to using it over recursively invoking setTimeout. (Check the millisecond timestamps in the console messages in the snippet below to verify this.)
In order to determine the constant interval argument you'll need for setInterval, you'll need to find the greatest common factor of your delay durations. Once you have this value, you can use it as the base interval delay, and keep track of your interval state to determine whether you should switch to the next interval delay, run your other code, etc. Here's a minimal example:
const durations = [1000, 5000];
// If you can't determine this in advance and use a constant value,
// then you can calculate it at runtime using a function:
const gcf = 1000; // or const gcf = findGreatestCommonFactor(durations);
let durationIndex = 0;
let elapsed = 0;
function update () {
elapsed += gcf;
const ready = elapsed === durations[durationIndex];
if (ready) {
elapsed = 0;
durationIndex = (durationIndex + 1) % durations.length;
}
return ready;
}
setInterval(() => {
const ready = update();
if (!ready) return;
// Do your interval task, for example:
console.log('tick');
}, gcf);
The problem with setInterval() is that the time is taken into account just once. You can use setTimeout() with recursion instead:
function doAction(flipFlop) {
setTimeout(() => {
console.log(flipFlop ? 'flip' : 'flop');
doAction(!flipFlop);
// do some other action...
}, flipFlop ? 1000 : 3000);
}
doAction(true);
Watch out though if you have a long running process, this recursion gets deeper and deeper.
I think this method is the easiest:
setInterval(() => {
console.log("first");
setTimeout(() => console.log("second"), 750);
}, 2000);
This creates an interval that alternates between 1250 and 750 milliseconds.
The problem with your code
let num = 5000
function alternateNum() {
if (num === 5000) { num = 1000 }
else { num === 5000 }
}
setInterval(() => {
// ...
alternateNum()
}, num);
The last few lines (the setInterval) call are only getting called once with the initial value of num and thus any future changes to num won't be reflected in the setTimeout call.
How to fix it
You should use setTimeout within the function that has your code and call your function recursively:
const doStuff = (time = 1000) => {
// Your code here
// generate the next time to wait
const nextTime = time === 5000 ? 1000 : 5000;
// call the function again after waiting `time` milliseconds
setInterval(() => doStuff(nextTime), time);
}
Then you would call doStuff to start it. If you wanted to start it immediately with the next one happening after 1 second you could do:
doStuff();
Or if you wanted to call it after 5 seconds with the next one happening one second after that:
setTimeout(doStuff, 5000);
The difference here compared to your code is that the variable that represents the time is being used over and over again as it changes instead of just once on initial code execution.
This is what my code looks like:
var fnInterval = setInterval(function() {
let b = true
if (b) {
console.log("hi")
} else {
console.log("bye")
}
b = !b
}, 1000);
clearTimeout(fnInterval, 10000)
I am a newbie to JavaScript and my aim here is to console log a message every 1 second for a total duration of 10 seconds, but during each interval I want my message to toggle its value between "hi" and "bye" . How can I do it? (as of now it displays the value for the default boolean and doesn't change later)
Move the flag variable out of the function:
let b = true;
const fnInterval = setInterval(function() {
if (b) {
console.log("hi");
} else {
console.log("bye");
}
b = !b
}, 1000);
To stop the timer after 10000 milliseconds, wrap the call to clearInterval in a setTimeout:
setTimeout(() => clearInterval(fnInterval), 10000);
Meanwhile, note that the return value of setInterval is not a function but a number, so it may be misleading to call it fnInterval.
First of all, declare let b = true outside of the callback function. It's re-initialized on each call otherwise.
Secondly, the 10000 in clearTimeout(fnInterval, 10000) isn't a valid parameter. clearTimeout(timeoutId) accepts only the first parameter and clears the timeout passed in immediately. You'd need a setTimeout to trigger this after 10 seconds, if that's your goal. But that causes a race condition between the two timeouts -- imprecision can mean you'll miss some of the logs or wind up with extra logs.
Using a counter is one solution, as other answers show, but usually when I'm using complex timing with setInterval that requires clearing it after some number of iterations, I refactor to a generic promisified sleep function based on setTimeout. This keeps the calling code much cleaner (no callbacks) and avoids messing with clearTimeout.
Instead of a boolean to flip a flag back and forth between two messages, a better solution is to use an array and modulus the current index by the messages array length. This makes it much easier to add more items to cycle through and the code is easier to understand since the state is implicit in the counter.
const sleep = ms => new Promise(res => setInterval(res, ms));
(async () => {
const messages = ["hi", "bye"];
for (let i = 0; i < 10; i++) {
console.log(messages[i%messages.length]);
await sleep(1000);
}
})();
setInterval() is stopped by clearInterval() not clearTimeout(). Details are commented in code below.
// Define a counter
let i = 0;
// Define interval function
const fnCount = setInterval(fnSwitch, 1000);
function fnSwitch() {
// Increment counter
i++;
// if counter / 2 is 0 log 'HI'
if (i % 2 === 0) {
console.log(i + ' HI');
// Otherwise log 'BYE'
} else {
console.log(i + ' BYE');
}
// If counter is 10 or greater run fnStop()
if (i >= 10) {
fnStop();
}
};
function fnStop() {
// Stop the interval function fnCount()
clearInterval(fnCount);
};
I'm writing some Javascript that interacts with library code that I don't own, and can't (reasonably) change. It creates Javascript timeouts used for showing the next question in a series of time-limited questions. This isn't real code because it is obfuscated beyond all hope. Here's what the library is doing:
....
// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = setTimeout( showNextQuestion(questions[i+1]), t );
I want to put a progress bar onscreen that fills towards questionTime * 1000 by interrogating the timer created by setTimeout. The only problem is, there seems to be no way to do this. Is there a getTimeout function that I'm missing? The only information on Javascript timeouts that I can find is related only to creation via setTimeout( function, time) and deletion via clearTimeout( id ).
I'm looking for a function that returns either the time remaining before a timeout fires, or the time elapsed after a timeout has been called. My progress bar code looks like this:
var timeleft = getTimeout( test.currentTimeout ); // I don't know how to do this
var $bar = $('.control .bar');
while ( timeleft > 1 ) {
$bar.width(timeleft / test.defaultQuestionTime * 1000);
}
tl;dr: How do I find the time remaining before a javascript setTimeout()?
Here's the solution I'm using now. I went through the library section that's in charge of tests, and unscrambled the code (terrible, and against my permissions).
// setup a timeout to go to the next question based on user-supplied time
var t = questionTime * 1000
test.currentTimeout = mySetTimeout( showNextQuestion(questions[i+1]), t );
and here's my code:
// wrapper for setTimeout
function mySetTimeout( func, timeout ) {
timeouts[ n = setTimeout( func, timeout ) ] = {
start: new Date().getTime(),
end: new Date().getTime() + timeout
t: timeout
}
return n;
}
This works pretty spot-on in any browser that isn't IE 6. Even the original iPhone, where I expected things to get asynchronous.
Just for the record, there is a way to get the time left in node.js:
var timeout = setTimeout(function() {}, 3600 * 1000);
setInterval(function() {
console.log('Time left: '+getTimeLeft(timeout)+'s');
}, 2000);
function getTimeLeft(timeout) {
return Math.ceil((timeout._idleStart + timeout._idleTimeout - Date.now()) / 1000);
}
Prints:
$ node test.js
Time left: 3599s
Time left: 3597s
Time left: 3595s
Time left: 3593s
This doesn't seem to work in firefox through, but since node.js is javascript, I thought this remark might be helpful for people looking for the node solution.
EDIT: I actually think I made an even better one: https://stackoverflow.com/a/36389263/2378102
I wrote this function and I use it a lot:
function timer(callback, delay) {
var id, started, remaining = delay, running
this.start = function() {
running = true
started = new Date()
id = setTimeout(callback, remaining)
}
this.pause = function() {
running = false
clearTimeout(id)
remaining -= new Date() - started
}
this.getTimeLeft = function() {
if (running) {
this.pause()
this.start()
}
return remaining
}
this.getStateRunning = function() {
return running
}
this.start()
}
Make a timer:
a = new timer(function() {
// What ever
}, 3000)
So if you want the time remaining just do:
a.getTimeLeft()
If you can't modify the library code, you'll need to redefine setTimeout to suit your purposes. Here's an example of what you could do:
(function () {
var nativeSetTimeout = window.setTimeout;
window.bindTimeout = function (listener, interval) {
function setTimeout(code, delay) {
var elapsed = 0,
h;
h = window.setInterval(function () {
elapsed += interval;
if (elapsed < delay) {
listener(delay - elapsed);
} else {
window.clearInterval(h);
}
}, interval);
return nativeSetTimeout(code, delay);
}
window.setTimeout = setTimeout;
setTimeout._native = nativeSetTimeout;
};
}());
window.bindTimeout(function (t) {console.log(t + "ms remaining");}, 100);
window.setTimeout(function () {console.log("All done.");}, 1000);
This is not production code, but it should put you on the right track. Note that you can only bind one listener per timeout. I haven't done extensive testing with this, but it works in Firebug.
A more robust solution would use the same technique of wrapping setTimeout, but instead use a map from the returned timeoutId to listeners to handle multiple listeners per timeout. You might also consider wrapping clearTimeout so you can detach your listener if the timeout is cleared.
Server side Node.js specific
None of the above really worked for me, and after inspecting the timeout object it looked like everything was relative to when the process started. The following worked for me:
myTimer = setTimeout(function a(){console.log('Timer executed')},15000);
function getTimeLeft(timeout){
console.log(Math.ceil((timeout._idleStart + timeout._idleTimeout)/1000 - process.uptime()));
}
setInterval(getTimeLeft,1000,myTimer);
Output:
14
...
3
2
1
Timer executed
-0
-1
...
node -v
v9.11.1
Edited output for brevity, but this basic function gives a approximate time until execution or since execution. As others mention, none of this will be exact due to the way node processes, but if I want to suppress a request that was run less than 1 minute ago, and I stored the timer, I don't see why this wouldn't work as a quick check. Could be interesting to juggle objects with refreshtimer in 10.2+.
Javascript's event stacks don't operate how you would think.
When a timeout event is created, it is added to the event queue, but other events may take priority while that event is being fired, delay the execution time and postponing runtime.
Example: You create a timeout with a delay of 10 seconds to alert something to the screen. It will be added to the event stack and will be executed after all current events are fired (causing some delay). Then, when the timeout is processed, the browser still continues to capture other events add them to the stack, which causes further delays in the processing. If the user clicks, or does a lot of ctrl+typing, their events take priority over the current stack. Your 10 seconds can turn into 15 seconds, or longer.
That being said, there are many ways to fake how much time has passed. One way is to execute a setInterval right after you add the setTimeout to the stack.
Example: Perform a settimeout with a 10 second delay (store that delay in a global). Then perform a setInterval that runs every second to subtract 1 from the delay and output the delay remaining. Because of how the event stack can influence actual time (described above), this still won't be accurate, but does give a count.
In short, there is no real way to get the remaining time. There are only ways to try and convey an estimate to the user.
A quicker, easier way:
tmo = 1000;
start = performance.now();
setTimeout(function(){
foo();
},tmo);
You can get the time remaining with:
timeLeft = tmo - (performance.now() - start);
I stopped by here looking for this answer, but was overthinking my problem. If you are here because you just need to keep track of time while you're setTimeout is in progress, here's another way to do it:
var focusTime = parseInt(msg.time) * 1000
setTimeout(function() {
alert('Nice Job Heres 5 Schrute bucks')
clearInterval(timerInterval)
}, focusTime)
var timerInterval = setInterval(function(){
focusTime -= 1000
initTimer(focusTime / 1000)
}, 1000);
You can modify setTimeout to store each timeout's end time in a map and create a function called getTimeout to get the time left for a timeout with a certain id.
This was super's solution, but I modified it to use slightly less memory
let getTimeout = (() => { // IIFE
let _setTimeout = setTimeout, // Reference to the original setTimeout
map = {}; // Map of all timeouts with their end times
setTimeout = (callback, delay) => { // Modify setTimeout
let id = _setTimeout(callback, delay); // Run the original, and store the id
map[id] = Date.now() + delay; // Store the end time
return id; // Return the id
};
return (id) => { // The actual getTimeout function
// If there was no timeout with that id, return NaN, otherwise, return the time left clamped to 0
return map[id] ? Math.max(map[id] - Date.now(), 0) : NaN;
}
})();
Usage:
// go home in 4 seconds
let redirectTimeout = setTimeout(() => {
window.location.href = "/index.html";
}, 4000);
// display the time left until the redirect
setInterval(() => {
document.querySelector("#countdown").innerHTML = `Time left until redirect ${getTimeout(redirectTimeout)}`;
},1);
Here's a minified version of this getTimeout IIFE:
let getTimeout=(()=>{let t=setTimeout,e={};return setTimeout=((a,o)=>{let u=t(a,o);return e[u]=Date.now()+o,u}),t=>e[t]?Math.max(e[t]-Date.now(),0):NaN})();
I hope this is as useful to you as it was for me! :)
No, but you can have your own setTimeout/setInterval for animation in your function.
Say your question looks like this:
function myQuestion() {
// animate the progress bar for 1 sec
animate( "progressbar", 1000 );
// do the question stuff
// ...
}
And your animation will be handled by these 2 functions:
function interpolate( start, end, pos ) {
return start + ( pos * (end - start) );
}
function animate( dom, interval, delay ) {
interval = interval || 1000;
delay = delay || 10;
var start = Number(new Date());
if ( typeof dom === "string" ) {
dom = document.getElementById( dom );
}
function step() {
var now = Number(new Date()),
elapsed = now - start,
pos = elapsed / interval,
value = ~~interpolate( 0, 500, pos ); // 0-500px (progress bar)
dom.style.width = value + "px";
if ( elapsed < interval )
setTimeout( step, delay );
}
setTimeout( step, delay );
}
If anyone's looking back on this. I've come out with a timeout and interval manager that can get you the time left in a timeout or interval as well as do some other stuff. I'll be adding to it to make it more nifty and more accurate, but it seems to work fairly well as is (although I have some more ideas to make it even more accurate):
https://github.com/vhmth/Tock
Question has already been answered but I will add my bit. It just occured to me.
Use setTimeout in recursion as follows:
var count = -1;
function beginTimer()
{
console.log("Counting 20 seconds");
count++;
if(count <20)
{
console.log(20-count+"seconds left");
setTimeout(beginTimer,2000);
}
else
{
endTimer();
}
}
function endTimer()
{
console.log("Time is finished");
}
I guess the code is self explanatory
Check this one:
class Timer {
constructor(fun,delay) {
this.timer=setTimeout(fun, delay)
this.stamp=new Date()
}
get(){return ((this.timer._idleTimeout - (new Date-this.stamp))/1000) }
clear(){return (this.stamp=null, clearTimeout(this.timer))}
}
Make a timer:
let smtg = new Timer(()=>{do()}, 3000})
Get remain:
smth.get()
Clear timeout
smth.clear()
(function(){
window.activeCountdowns = [];
window.setCountdown = function (code, delay, callback, interval) {
var timeout = delay;
var timeoutId = setTimeout(function(){
clearCountdown(timeoutId);
return code();
}, delay);
window.activeCountdowns.push(timeoutId);
setTimeout(function countdown(){
var key = window.activeCountdowns.indexOf(timeoutId);
if (key < 0) return;
timeout -= interval;
setTimeout(countdown, interval);
return callback(timeout);
}, interval);
return timeoutId;
};
window.clearCountdown = function (timeoutId) {
clearTimeout(timeoutId);
var key = window.activeCountdowns.indexOf(timeoutId);
if (key < 0) return;
window.activeCountdowns.splice(key, 1);
};
})();
//example
var t = setCountdown(function () {
console.log('done');
}, 15000, function (i) {
console.log(i / 1000);
}, 1000);
For anyone in need of a hook, check this out - should be pretty self explanatory.
Note that elapsed is an internal state variable that if passed outside of the hook will be incorrect!
import { useEffect, useRef, useState } from 'react';
const useTimeout = (callback, duration, renderDuration = 5) => {
const ref = useRef<any>(null);
const [timeInfo, setTimeInfo] = useState<{
start: number;
elapsed: number;
percentComplete: number;
}>({
start: null,
elapsed: 0,
percentComplete: 0
});
useEffect(() => {
return () => {
if (ref.current) {
clearTimeout(ref.current);
ref.current = null;
}
};
}, []);
useEffect(() => {
setTimeout(() => {
if (ref.current == null) return;
setTimeInfo((prev) => {
const elapsed = Date.now() - prev.start + prev.elapsed;
if (ref.current == null) return prev;
return {
start: prev.start,
elapsed: prev.elapsed,
percentComplete: (elapsed / duration) * 100
};
});
}, renderDuration);
}, [timeInfo]);
return {
percentComplete: timeInfo.percentComplete,
isTimerRunning: ref.current != null,
startTimeout: () => {
if (ref.current != null) return;
setTimeInfo((prev) => ({ ...prev, start: Date.now() }));
ref.current = setTimeout(callback, duration - timeInfo.elapsed);
},
stopTimeout: () => {
if (ref.current) {
clearTimeout(ref.current);
ref.current = null;
}
setTimeInfo((prev) => {
const elapsed = Date.now() - prev.start + prev.elapsed;
return {
start: prev.start,
elapsed: elapsed,
percentComplete: (elapsed / duration) * 100
};
});
},
resetTimeout: () => {
if (ref.current) {
ref.current = null;
clearTimeout(ref.current);
}
setTimeInfo({ start: null, elapsed: 0, percentComplete: 0 });
},
restartTimeout: () => {
if (ref.current) {
ref.current = null;
clearTimeout(ref.current);
}
setTimeInfo({ start: Date.now(), elapsed: 0, percentComplete: 0 });
ref.current = setTimeout(callback, duration);
}
};
};
export default useTimeout;