Is there a faster alternative to window.requestAnimationFrame()
for endless loops that don't block I/O?
What I'm doing in the loop isn't related to animation so I don't care when the next frame is ready, and I have read that window.requestAnimationFrame()
is capped by the monitor's refresh rate or at least waits until a frame can be drawn.
I have tried the following as well:
function myLoop() {
// stuff in loop
setTimeout(myLoop, 4);
}
(The 4 is because that is the minimum interval in setTimeout
and smaller values will still default to 4.) However, I need better resolution than this.
Is there anything with even better performance out there?
I basically need a non-blocking version of while(true)
.
Is there a faster alternative to window.requestAnimationFrame()
for endless loops that don't block I/O?
What I'm doing in the loop isn't related to animation so I don't care when the next frame is ready, and I have read that window.requestAnimationFrame()
is capped by the monitor's refresh rate or at least waits until a frame can be drawn.
I have tried the following as well:
function myLoop() {
// stuff in loop
setTimeout(myLoop, 4);
}
(The 4 is because that is the minimum interval in setTimeout
and smaller values will still default to 4.) However, I need better resolution than this.
Is there anything with even better performance out there?
I basically need a non-blocking version of while(true)
.
-
"(The 4 is because that is the minimum interval in setTimeout and smaller values will still default to 4.)" It's more plicated than that, and has changed at least twice. Just use
0
and let the implementation worry about whether to increase it. – T.J. Crowder Commented Mar 2, 2017 at 4:49 - @T.J. I'm using Electron, so I can access node modules and window.blahblah simultaneously :) – Joey Commented Mar 2, 2017 at 4:52
- If you need something to run more often than 250 times a second, why don't you just make it run several iterations at once, then allow for I/O, repeat? If you want a true resolution of sub-four milliseconds, i think you are programming in the wrong language on the wrong operating system. – ASDFGerte Commented Mar 2, 2017 at 5:16
2 Answers
Reset to default 5Two things that will run sooner than that setTimeout
:
process.nextTick
callbacks (NodeJS-specific):The
process.nextTick()
method adds the callback to the "next tick queue". Once the current turn of the event loop turn runs to pletion, all callbacks currently in the next tick queue will be called.This is not a simple alias to
setTimeout(fn, 0)
. It is much more efficient. It runs before any additional I/O events (including timers) fire in subsequent ticks of the event loop.Promise settlement notifications
So those might be a tools for your toolbelt, doing a mix of one or both of those with setTimeout
to achieve the balance you want.
Details:
As you probably know, a given JavaScript thread runs on the basis of a task queue (the spec calls it a job queue); and as you probably know, there's one main default UI thread in browsers and NodeJS runs a single thread.
But in fact, there are at least two task queues in modern implementations: The main one we all think of (where setTimeout
and event handlers put their tasks), and the "microtask" queue where certain async operations are placed during the processing of a main task (or "macrotask"). Those microtasks are processed as soon as the macrotask pletes, before the next macrotask in the main queue — even if that next macrotask was queued before the microtasks were.
nextTick
callbacks and promise settlement notifications are both microtasks. So scheduling either schedules an async callback, but one which will happen before the next main task.
We can see that in the browser with setInterval
and a promise resolution chain:
let counter = 0;
// setInterval schedules macrotasks
let timer = setInterval(() => {
$("#ticker").text(++counter);
}, 100);
// Interrupt it
$("#hog").on("click", function() {
let x = 300000;
// Queue a single microtask at the start
Promise.resolve().then(() => console.log(Date.now(), "Begin"));
// `next` schedules a 300k microtasks (promise settlement
// notifications), which jump ahead of the next task in the main
// task queue; then we add one at the end to say we're done
next().then(() => console.log(Date.now(), "End"));
function next() {
if (--x > 0) {
if (x === 150000) {
// In the middle; queue one in the middle
Promise.resolve().then(function() {
console.log(Date.now(), "Middle");
});
}
return Promise.resolve().then(next);
} else {
return 0;
}
}
});
$("#stop").on("click", function() {
clearInterval(timer);
});
<div id="ticker"> </div>
<div><input id="stop" type="button" value="Stop"></div>
<div><input id="hog" type="button" value="Hog"></div>
<script src="https://ajax.googleapis./ajax/libs/jquery/2.1.1/jquery.min.js"></script>
When you run that and click the Hog button, note how the counter display freezes, then keeps going again. That's because of the 300,000 microtasks that get scheduled ahead of it. Also note the timestamps on the three log messages we write (they don't appear in the snippet console until a macrotask displays them, but the timestamps show us when they were logged).
So basically, you could schedule a bunch of microtasks, and periodically let those run out and run the next macrotask.
Note: I've used setInterval
for the browser example in the snippet, but setInterval
, specifically, may not be a good choice for a similar experiment using NodeJS, as NodeJS's setInterval
is a bit different from the one in browsers and has some surprising timing characteristics.
there are some libs that can work like cron task, e.g., https://www.npmjs./package/node-cron
i think that using cron should be easier, and more flexible.