According to documentation the actual delay of setTimeout may be longer than was asked. Could you please point me on documentation or question with answer which explains may the actual delay of setTimeout be shorter than was asked?
Thing is I encountered an issue which happened really seldom and could be explained by such phenomenon. Platforms: Chrome Version 67, NodeJS Version 9.8.0. Also, I am really curious is this statement true for Firefox and other browsers?
You can test a timeout or interval accuracy by taking a delta of two microtimes. In my tests, the timers are plus or minus a few milliseconds. Run the program below to see the results in your own environment.
In a perfect world, the output would always show 1000. The variance in the output means our world is imperfect 😂
var last = Date.now()
var interval = setInterval(function() {
var now = Date.now()
var delta = now - last
console.log(delta)
last = now
}, 1000)
setTimeout(clearInterval, 10000, interval)
// 1000
// 1003
// 998
// 1002
// 999
// 1007
// 1001
// ...
To dramatically affect the result, press Run, switch to another tab, then come back to this tab after a few seconds. You'll see de-focused tabs have extremely high variance.
// 1004 <-- start experiment
// 997
// 1000 <-- switch to another tab
// 1533 <-- variance spikes immediately
// 866
// 1033
// 568 <-- switch back to this tab
// 1001 <-- variance restabilizes
// 1000
// 999
I don't know all of the things that play a role in affecting the accuracy of timeouts and intervals in JavaScript, but I also don't think that's an important thing to know. Ultimately we don't need accuracy because we can calculate precise durations of time using the delta technique above.
a practical example in React
Below we make a simple Timer component which naively uses setInterval to refresh the timer's display once per second...
class Timer extends React.Component {
constructor (props) {
super (props)
this.state = { seconds: 0, timeout: null }
}
componentDidMount () {
this.setState ({
timeout: setInterval (this.tick.bind(this), 1000)
})
}
componentWillUnmount () {
clearTimeout (this.timeout)
}
tick () {
this.setState ({ seconds: this.state.seconds + 1 })
}
render () {
return <div>Naive timer: {this.state.seconds}</div>
}
}
ReactDOM.render
( <Timer />
, document.getElementById ('timer')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>
But due to the unreliable nature of JavaScript's timers, we know that our Timer component will eventually display the incorrect value.
When we implement PreciseTimer below, we can use our delta technique to ensure the component always displays the correct duration
class PreciseTimer extends React.Component {
constructor (props) {
super (props)
this.state = { start: Date.now (), seconds: 0, timeout: null }
}
componentDidMount () {
this.setState ({
timeout: setInterval (this.tick.bind(this), 1000)
})
}
componentWillUnmount () {
clearTimeout (this.timeout)
}
tick () {
const delta = Date.now () - this.state.start
this.setState ({ seconds: delta / 1000 })
}
render () {
return <div>Precise timer: {this.state.seconds}</div>
}
}
ReactDOM.render
( <PreciseTimer />
, document.getElementById ('timer')
)
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="timer"></div>
To see the practical difference in how these two timers behave, start both of them, switch to a new tab for 10-15 seconds, then switch back to this tab. The naive Timer will suffer from JavaScript timer variance whereas PreciseTimer will always display the correct duration.
Actually setTimeout will just push the callback on top of the event loop after the specified delay, but because Javascript is mono process if something block the event loop the callback will be delayed. But it can't be shorter or it's a bug in the code / javascript engine used.
Javascript's single execution thread means that sometimes things that you queue up to be executed in the future at point x, might actually be executed on time x+, since there might be no execution-time-frame available.
However things will never be able to execute BEFORE the required time, only at-or-after your given time
A setTimeout(myFunc,100) for example may execute after 100 ms, or a little bit longer.
You can find more information on this article regarding javascrip's timers in general:https://johnresig.com/blog/how-javascript-timers-work/
To complete user633183's answer, here is a timer that ensures your function never runs before given time interval:
class Timer {
constructor() {
this.last = Date.now();
this.interval = null;
}
start (f, ms) {
// Stop previous timer (if any)
this.stop();
// Start new timer
this.interval = setInterval( () => {
const now = Date.now();
// Don't execute before given time interval
if (now - this.last < ms)
return;
this.last = now;
f();
}, ms);
}
stop () {
if (this.interval)
clearInterval(this.interval);
this.interval = null;
}
}
const timer = new Timer();
timer.start( () => console.log(Date.now()), 1000);
Related
I'm attempting to make a basic count-down timer in React. It should start at 30 seconds, and count down by 1 each second. When clicking the button, the timer should restart at 30 and begin the countdown again.
This seems simple enough, and it's printing to the console exactly as I expect it to. However, as soon as I try to update state with my timer so I can render the countdown on-screen (uncomment the commented line below) the console.log duplicates, the render doubles, and I seem to have two states running simultaneously. I'm not sure what to do about this.
Any help is greatly appreciated!
const [seconds, setSeconds] = useState(30)
let interval = null
function startTimer() {
stopTimer()
let start = 30
interval = setInterval(() => {
// setSeconds(start)
console.log('start: ', start)
start--
}, 1000)
}
function stopTimer() {
clearInterval(interval)
}
return (
<p>{seconds}s</p>
<button onClick={startTimer}>Start</button>
)
I've looked around to see what I could find myself before posting. I read through a number of articles on React and setInterval, and watched some tutorials, but couldn't find what I was looking for. I attempted to rewrite the code in different ways but always ended with the same result.
There are multiple things to say, like why use async/await when there is nothing to await for, why use a local variable start = 30 when you just want to decrease your seconds count and why you declare the interval in the function body. A React functional component will run all its code and in your case do let interval = null everytime it rerenders. You have to store the interval somewhere else, like here as a global variable. Moreover, when you create the setInterval, it won't have access to the new seconds count. What you can do is use the arrow function form inside your setState function. Doing so, you will get the right seconds variable.
Maybe the code below will help you find out what's wrong:
let interval = null
function App(props) {
const [seconds, setSeconds] = React.useState(30)
function startTimer() {
stopTimer()
interval = setInterval(() => {
setSeconds((seconds) => seconds - 1)
}, 1000)
}
function stopTimer() {
clearInterval(interval)
setSeconds(30)
}
return (<button onClick={startTimer}>{seconds}</button>)
}
Thanks for the help folks!
Turns out, regardless of how I assign the state to seconds (arrow function or a separate variable) the cause of the issue here was the placement of the 'interval' variable.
Thomas's answer was correct. Moving it outside of the functional component rather than inside corrected the issue. If the variable was within the function the interval seemed like it didn't fully clear, just paused, and then there were two intervals running simultaneously.
Here's the final code, functioning as expected.
import { useState } from "react"
let interval = null
export default function app() {
const [seconds, setSeconds] = useState(30)
function startTimer() {
stopTimer()
interval = setInterval(() => {
setSeconds((seconds) => seconds - 1)
}, 1000)
}
function stopTimer() {
clearInterval(interval)
setSeconds(30)
}
return (
<p>{seconds}s</p>
<button onClick={startTimer}>Start</button>
</div>
)
}
In our codebase, we are using a RAFTimer to report render perf markers. The code looks like the following, callback would send the render perf markers. I was confused about Why we need requestAnimationFrame(rAF) here. setTimeout would be performed after the sync code(rendering logic), I suppose the timing of setTimeout is just right. What's the purpose of wrapping with an rAF?
export function rafTimer(callback: Function): void {
requestAnimationFrame(() =>
setTimeout(() => callback(), 0)
);
}
requestAnimationFrame(cb) schedules cb to be called just before the next page rendering.
So when the callback is executed, the rendering wasn't done yet.
However, scheduling a 0ms timeout from the rAF callback does allow to fire a function after the rendering happened.
To measure how long the rendering actually took, you indeed want to get the time both before and after the rendering occurred.
Note that there used to be a requestPostAnimationFrame proposal, so that we could have a native hook to this exact place in the event-loop, but it seems it hasn't moved a lot in the last few years, so I wouldn't hold my breath until it's widely supported. But you can still use the polyfill I made for this other SO Q/A, which does use a faster task scheduling than setTimeout, so that we're more confident to really be as close as possible from the rendering step.
Also, to ensure that your rAF callback is fired as late as possible, you'll want it to be scheduled from that postAnimationFrame callback (i.e the one from setTimeout), so that there is as few callbacks firing in between as possible. Scheduling it from the rAF callback directly, you could have other raF loops stacked after your callback and you would also measure the JS execution of these functions instead of measuring only the rendering time of your page. Note that even doing so, you can still have rAF callbacks scheduled later, for instance if they are scheduled from a click event.
// requestPostAnimationFrame polyfill
// from https://stackoverflow.com/a/57549862/3702797
"function"!=typeof requestPostAnimationFrame&&(()=>{const a=new MessageChannel,b=[];let c=0,d=!1,e=!1,f=!1;a.port2.onmessage=()=>{d=!1;const a=b.slice();b.length=0,a.forEach(a=>{try{a(c)}catch(a){}})};const g=globalThis.requestAnimationFrame;globalThis.requestAnimationFrame=function(...a){e||(e=!0,g.call(globalThis,a=>f=a),globalThis.requestPostAnimationFrame(()=>{e=!1,f=!1})),g.apply(globalThis,a)},globalThis.requestPostAnimationFrame=function(e){if("function"!=typeof e)throw new TypeError("Argument 1 is not callable");b.push(e),d||(f?(c=f,a.port1.postMessage("")):requestAnimationFrame(b=>{c=b,a.port1.postMessage("")}),d=!0)}})();
// measure as close as possible the time it took for rendering the page
function measureRenderingTime(cb) {
let t1, t2;
const rAFCallback = () => {
requestPostAnimationFrame(rPAFCallback);
t1 = performance.now(); // before rendering
};
const rPAFCallback = () => {
t2 = performance.now(); // after rendering
cb(t2 - t1);
};
requestAnimationFrame(rAFCallback);
}
// do something with the measurements
// (here simple max + average over 50 occurrences)
const times = [];
const handleRenderingTime = (time) => {
times.push(time);
while (times.length > 50) {
times.shift();
}
document.querySelector("pre").textContent = `last: ${ time.toFixed(2) }
max: ${ Math.max(...times).toFixed(2) }
avg: ${ (times.reduce((t,v) => t+v, 0) / times.length).toFixed(2) }`;
// loop
measureRenderingTime(handleRenderingTime);
};
measureRenderingTime(handleRenderingTime); // begin the loop
// simulate an actual rAF loop, with sometimes long JS execution time
let longFrame = false;
document.querySelector("button").onclick = (evt) => longFrame = true;
const animLoop = () => {
if (longFrame) {
const t1 = performance.now();
// lock the browser for 300ms
// this is actually only JS, that shouldn't impact the rendering time by much
while (performance.now() - t1 < 300) {}
};
longFrame = false;
requestAnimationFrame(animLoop);
};
requestAnimationFrame(animLoop);
<pre id=log></pre>
<button>perform a long rAF callback</button>
When you click the start button, it starts two counters. One uses recursion and the other uses a for loop. Why does the looping counter block the counting of the recursive counter? Also, why is there a limit for the number of iterations in the for loop?
import React from 'react'
export default class Clock extends React.Component {
state = {
recursiveCount: 0,
loopCount: 0,
stop: false
}
clickStart = () => {
this.setState({
stop: false
}, () => {
this.startRecursion()
this.startLooping()
})
}
startLooping = () => {
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
this.setState({
loopCount: this.state.loopCount + 1
})
}, 1)
}
}
stop = () => {
this.setState({
stop: true
})
}
startRecursion = () => {
if (!this.state.stop) {
setTimeout(() => {
this.setState({
recursiveCount: this.state.recursiveCount + 1
}, () => {
this.startRecursion()
})
}, 1)
}
}
render () {
return (
<div>
<button className = 'button' onClick =
{this.clickStart}>Start</button>
<button className = 'button' id = 'stop' onClick =
{this.stop}>Stop</button>
<h1>The recursive function has been recursively called {this.state.recursiveCount} times</h1>
<h1> The iterative function has been iteratively called {this.state.loopCount} times </h1>
</div>
)
}
}
Lets step through the code:
for (let i = 0; i < 1000; i++) {
It loops 1000 times. Thats much and will probably hang the browser for a bit.
setTimeout(/*callback*/, 1);
You add a lot of timeouts to the queue. Now the code is done. One tick later, all timeouts fire at the same time, and will be executed directly one after another. Thats why the counter directly jumps from 1000 to 2000.
Why does the looping counter block the counting of the recursive counter?
Cause there is only one thread, and that will be blocked by the 1000 callbacks that all get into the threads queue at the same time. The recursive timeout will also end in the queue, therefore it will only be executed after the 1000 others, which takes time.
Also, why is there a limit for the number of iterations in the for loop?
There is no number of limitations in a loop, however if you block the thread for too long it crashes to help the client:
while(true );
Additionally as chris pointed out, calling setState too often in a loop might get you in trouble.
I don't think you want
for (let i = 0; i < 1000; i++) {
setTimeout(() => {
this.setState({
loopCount: this.state.loopCount + 1
})
}, 1)
}
as that queues 1000 timeouts without waiting for the previous timeout to finish. And, additionally, is blocking.
Here's a working codesandbox with what I think you're going for: https://codesandbox.io/s/2xy00529jp
I took a little bit of a different approach - I'm keeping track of the timeouts and clearing them on stop - I don't think there's anything wrong with using a flag (this.state.stop), but I find my way a little cleaner. Additionally, I'm using the callback for of this.setState - this is super important when doing a large number of sequential state updates where the new state value relies on the previous state value. Take a look through this page for more info.
I would like to create a delay function in javascript that takes a parameter of amount of time to delay, so that I could use it do introduce delay between execution of JavaScript lines in my QML application. It would perhaps look like this:
function delay(delayTime) {
// code to create delay
}
I need the body of the function delay(). Note that setTimeout() of JavaScript doesn't work in QML.
As suggested in the comments to your question, the Timer component is a good solution to this.
function Timer() {
return Qt.createQmlObject("import QtQuick 2.0; Timer {}", root);
}
timer = new Timer();
timer.interval = 1000;
timer.repeat = true;
timer.triggered.connect(function () {
print("I'm triggered once every second");
})
timer.start();
The above would be how I'm currently using it, and here's how I might have implemented the example in your question.
function delay(delayTime) {
timer = new Timer();
timer.interval = delayTime;
timer.repeat = false;
timer.start();
}
(Which doesn't do anything; read on)
Though the exact way you are looking for it to be implemented suggests that you are looking for it to block until the next line of your program executes. But this isn't a very good way to go about it as it would also block everything else in your program as JavaScript only runs in a single thread of execution.
An alternative is to pass a callback.
function delay(delayTime, cb) {
timer = new Timer();
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
Which would allow you to use it as such.
delay(1000, function() {
print("I am called one second after I was started.");
});
Hope it helps!
Edit: The above assumes you're working in a separate JavaScript file that you later import into your QML file. To do the equivalent in a QML file directly, you can do this.
import QtQuick 2.0
Rectangle {
width: 800
height: 600
color: "brown"
Timer {
id: timer
}
function delay(delayTime, cb) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.start();
}
Rectangle {
id: rectangle
color: "yellow"
anchors.fill: parent
anchors.margins: 100
opacity: 0
Behavior on opacity {
NumberAnimation {
duration: 500
}
}
}
Component.onCompleted: {
print("I'm printed right away..")
delay(1000, function() {
print("And I'm printed after 1 second!")
rectangle.opacity = 1
})
}
}
I'm not convinced that this is the solution to your actual problem however; to delay an animation, you could use PauseAnimation.
Marcus' answer does the job, but there is one big problem.
The problem is that the callback keeps connected to triggered signal even after triggered once. This means that if you use that delay function again, the timer will triggers all callbacks connected before again. So you should disconnect the callback after triggered.
This is my enhanced version of the delay function:
Timer {
id: timer
function setTimeout(cb, delayTime) {
timer.interval = delayTime;
timer.repeat = false;
timer.triggered.connect(cb);
timer.triggered.connect(function release () {
timer.triggered.disconnect(cb); // This is important
timer.triggered.disconnect(release); // This is important as well
});
timer.start();
}
}
...
timer.setTimeout(function(){ console.log("triggered"); }, 1000);
Here's another variation which utilizes the Component object to house the Timer object.
Then we implement a setTimeout look-a-like function to dynamically create and invoke this Timer object.
N.B. The answer assumes Qt5.12.x which includes ECMAScript 7 (and therefore ECMAScript 6) to utilize parameter shortcuts, rest parameters and spread syntax:
function setTimeout(func, interval, ...params) {
return setTimeoutComponent.createObject(app, { func, interval, params} );
}
function clearTimeout(timerObj) {
timerObj.stop();
timerObj.destroy();
}
Component {
id: setTimeoutComponent
Timer {
property var func
property var params
running: true
repeat: false
onTriggered: {
func(...params);
destroy();
}
}
}
In the following snippet, we will invoke console.log(31), console.log(32), console.log(33) in a random time delay between 0-1000ms from now.
console.log("Started");
setTimeout(console.log, Math.floor(1000 * Math.random()), 31);
setTimeout(console.log, Math.floor(1000 * Math.random()), 32);
setTimeout(console.log, Math.floor(1000 * Math.random()), 33);
See also: https://community.esri.com/groups/appstudio/blog/2019/05/22/ecmascript-7-settimeout-and-arrow-functions
The answer from Bumsik Kim is great, this answer changes it slightly so that the timer can be used on a repeating basis and then stopped and reused when desired.
The QML for the timer to add where required.
// Allow outside access (optional)
property alias timer: timer
Timer {
id: timer
// Start the timer and execute the provided callback on every X milliseconds
function startTimer(callback, milliseconds) {
timer.interval = milliseconds;
timer.repeat = true;
timer.triggered.connect(callback);
timer.start();
}
// Stop the timer and unregister the callback
function stopTimer(callback) {
timer.stop();
timer.triggered.disconnect(callback);
}
}
This can be used as follows.
timer.startTimer(Foo, 1000); // Run Foo every 1 second
timer.stopTimer(Foo); // Stop running Foo
timer.startTimer(Bar, 2000); // Run Bar every 2 seconds
timer.stopTimer(Bar); // Stop running Bar
function Foo() {
console.log('Executed Foo');
}
function Bar() {
console.log('Executed Bar');
}
Here's my continued evolution of the prior answers https://stackoverflow.com/a/62051450/3220983 and https://stackoverflow.com/a/50224584/3220983...
Add this file / component to your project:
Scheduler.qml
import QtQuick 2.0
Timer {
id: timer
property var _cbFunc: null
property int _asyncTimeout: 250
// Execute the callback asynchonously (ommiting a specific delay time)
function async( cbFunc )
{ delay( cbFunc, _asyncTimeout ) }
// Start the timer and execute the provided callback ONCE after X ms
function delay( cbFunc, milliseconds )
{ _start( cbFunc, milliseconds, false ) }
// Start the timer and execute the provided callback repeatedly every X ms
function periodic( cbFunc, milliseconds )
{ _start( cbFunc, milliseconds, true ) }
function _start( cbFunc, milliseconds, isRepeat ) {
if( cbFunc === null ) return
cancel()
_cbFunc = cbFunc
timer.interval = milliseconds
timer.repeat = isRepeat
timer.triggered.connect( cbFunc )
timer.start()
}
// Stop the timer and unregister the cbFunc
function cancel() {
if( _cbFunc === null ) return
timer.stop()
timer.triggered.disconnect( _cbFunc )
_cbFunc = null
}
}
Then, implement in another component like:
...
Scheduler { id: scheduler; }
scheduler.delay( function(){ console.log('Delayed'); }, 3000 );
You can use anonymous functions like shown here, or else callback into named functions. Use the simple async to fire off code in a non-blocking manner, if you aren't too concerned about the exact timing. Note that while it's tempting to use a 0 ms timeout for an "asynchronous" callback (as one would with a C++ QTimer), that is not the right approach with a QML Timer! These timers don't appear to queue events on an event loop where screen redraws are given priority. So, if your goal is to defer a given operation to achieve "instant" UI changes first, you need to dial up the delay interval as shown here. Using 0ms will often cause the code to fire prior to redraws.
Note that one of these "Scheduler" instances can only be bound to one callback function, on one given interval, at a time. Multiple instances are required if you need to "overlap" delayed events.
you can use QtTest
import QtTest 1.0
import QtQuick 2.9
ApplicationWindow{
id: window
TestEvent {
id: test
}
function delay_ms(delay_time) {
test.mouseClick(window, 0, 0, Qt.NoButton, Qt.NoModifier, delay_time)
}
}
This should be sufficient:
void QmlUtils::singleShot(int msec, QJSValue callback)
{
QTimer::singleShot(msec, this, [callback] () mutable {
if (callback.isCallable())
callback.call();
});
}
then call it in QML, wherever you are:
qmlUtils.singleShot(5000, () => console.log("Hello!"))
Done.
If you want, you can use this without even writing it. Simply expose it to QML with:
ctx->setContextProperty("lqtUtils", new lqt::QmlUtils(qApp));
How could I accurately run a function when the minute changes? Using a setInterval could work if I trigger it right when the minute changes. But I'm worried setInterval could get disrupted by the event-loop in a long-running process and not stay in sync with the clock.
How can I run a function accurately when the minute changes?
First off, you should use setInterval for repeating timers, since it (tries to) guarantee periodic execution, i.e. any potential delays will not stack up as they will with repeated setTimeout calls. This will execute your function every minute:
var ONE_MINUTE = 60 * 1000;
function showTime() {
console.log(new Date());
}
setInterval(showTime, ONE_MINUTE);
Now, what we need to do is to start this at the exact right time:
function repeatEvery(func, interval) {
// Check current time and calculate the delay until next interval
var now = new Date(),
delay = interval - now % interval;
function start() {
// Execute function now...
func();
// ... and every interval
setInterval(func, interval);
}
// Delay execution until it's an even interval
setTimeout(start, delay);
}
repeatEvery(showTime, ONE_MINUTE);
This may be an idea. The maximum deviation should be 1 second. If you want it to be more precise, lower the milliseconds of setTimeout1.
setTimeout(checkMinutes,1000);
function checkMinutes(){
var now = new Date().getMinutes();
if (now > checkMinutes.prevTime){
// do something
console.log('nextminute arrived');
}
checkMinutes.prevTime = now;
setTimeout(checkChange,1000);
}
1 But, see also this question, about accuracy of timeouts in javascript
You can try to be as accurate as you can, setting a timeout each X milliseconds and check if the minute has passed and how much time has passed since the last invocation of the function, but that's about it.
You cannot be 100% sure that your function will trigger exactly after 1 minute, because there might be something blocking the event-loop then.
If it's something vital, I suggest using a cronjob or a separate Node.js process specifically for that (so you can make sure the event loop isn't blocked).
Resources:
http://www.sitepoint.com/creating-accurate-timers-in-javascript/
I've put up a possible solution for you:
/* Usage:
*
* coolerInterval( func, interval, triggerOnceEvery);
*
* - func : the function to trigger
* - interval : interval that will adjust itself overtime checking the clock time
* - triggerOnceEvery : trigger your function once after X adjustments (default to 1)
*/
var coolerInterval = function(func, interval, triggerOnceEvery) {
var startTime = new Date().getTime(),
nextTick = startTime,
count = 0;
triggerOnceEvery = triggerOnceEvery || 1;
var internalInterval = function() {
nextTick += interval;
count++;
if(count == triggerOnceEvery) {
func();
count = 0;
}
setTimeout(internalInterval, nextTick - new Date().getTime());
};
internalInterval();
};
The following is a sample usage that prints the timestamp once every minute, but the time drift is adjusted every second
coolerInterval(function() {
console.log( new Date().getTime() );
}, 1000, 60);
It's not perfect, but should be reliable enough.
Consider that the user could switch the tab on the browser, or your code could have some other blocking tasks running on the page, so a browser solution will never be perfect, it's up to you (and your requirements) to decide if it's reliable enough or not.
Tested in browser and node.js
sleeps until 2 seconds before minute change then waits for change
you can remove logging as it gets pretty cluttered in log otherwise
function onMinute(cb,init) {
if (typeof cb === 'function') {
var start_time=new Date(),timeslice = start_time.toString(),timeslices = timeslice.split(":"),start_minute=timeslices[1],last_minute=start_minute;
var seconds = 60 - Number(timeslices[2].substr(0,2));
var timer_id;
var spin = function (){
console.log("awake:ready..set..");
var spin_id = setInterval (function () {
var time=new Date(),timeslice = time.toString(),timeslices = timeslice.split(":"),minute=timeslices[1];
if (last_minute!==minute) {
console.log("go!");
clearInterval(spin_id);
last_minute=minute;
cb(timeslice.split(" ")[4],Number(minute),time,timeslice);
console.log("snoozing..");
setTimeout(spin,58000);
}
},100);
};
setTimeout(spin,(seconds-2)*1000);
if (init) {
cb(timeslice.split(" ")[4],Number(start_minute),start_time,timeslice,seconds);
}
}
}
onMinute(function (timestr,minute,time,timetext,seconds) {
if (seconds!==undefined) {
console.log("started waiting for minute changes at",timestr,seconds,"seconds till first epoch");
} else {
console.log("it's",timestr,"and all is well");
}
},true);
My first thought would be to use the Date object to get the current time. This would allow you to set your set interval on the minute with some simple math. Then since your worried about it getting off, every 5-10 min or whatever you think is appropriate, you could recheck the time using a new date object and readjust your set interval accordingly.
This is just my first thought though in the morning I can put up some code(its like 2am here).
This is a fairly straightforward solution ... the interval for the timeout is adjusted each time it's called so it doesn't drift, with a little 50ms safety in case it fires early.
function onTheMinute(callback) {
const remaining = 60000 - (Date.now() % 60000);
setTimeout(() => {
callback.call(null);
onTheMinute(callback);
}, remaining + (remaining < 50 ? 60000 : 0));
}
Here's yet another solution based on #Linus' post and #Brad's comment. The only difference is it's not working by calling the parent function recursively, but instead is just a combination of setInterval() and setTimeout():
function callEveryInterval(callback, callInterval){
// Initiate the callback function to be called every
// *callInterval* milliseconds.
setInterval(interval => {
// We don't know when exactly the program is going to starts
// running, initialize the setInterval() function and, from
// thereon, keep calling the callback function. So there's almost
// surely going to be an offset between the host's system
// clock's minute change and the setInterval()'s ticks.
// The *delay* variable defines the necessary delay for the
// actual callback via setTimeout().
let delay = interval - new Date()%interval
setTimeout(() => callback(), delay)
}, callInterval, callInterval)
}
Small, maybe interesting fact: the callback function only begins executing on the minute change after next.
The solution proposed by #Linus with setInterval is in general correct, but it will work only as long as between two minutes there are exactly 60 seconds. This seemingly obvious assumption breaks down in the presence of a leap second or, probably more frequently, if the code runs on a laptop that get suspended for a number of seconds.
If you need to handle such cases it is best to manually call setTimeout adjusting every time the interval. Something like the following should do the job:
function repeatEvery( func, interval ) {
function repeater() {
repeatEvery( func, interval);
func();
}
var now = new Date();
var delay = interval - now % interval;
setTimeout(repeater, delay);
}