问题
So it turns out the while loop blocks the event loop in JavaScript, which prevents it from being a viable solution to creating a run loop if you want your application to respond to external / async events.
My question is, what techniques are used to have a "run loop" be infinite / constant but still have high performance and not block the event loop.
I ran a quick test to see the performance of while
, setTimeout(fn, 0)
(browser), and setImmediate(fn)
(node.js).
var x = 1000
var isNode = typeof window == 'undefined'
function a() {
var start = (new Date()).getTime()
var q = 0
function next() {
q++
if (q < x) {
if (isNode) {
setImmediate(next)
} else {
setTimeout(next, 0)
}
} else {
var end = (new Date).getTime()
console.log('a', end - start)
}
}
next()
}
function b() {
var start = (new Date()).getTime()
var q = 0
while (q < x) {
q++
}
var end = (new Date).getTime()
console.log('b', end - start)
}
a()
b()
node.js:
a = 20
b = 0
browser:
a = 5039
b = 0
Is there not a way to do an "infinite while loop" that is as performant as doing a while loop in JavaScript. setImmediate
is much faster than setTimeout
, but it's still a lot slower than the basic while loop.
回答1:
You can yield to the event loop inside a while loop with async/await:
// This can only be logged when the event loop
// is yielded to in the infinite loop below.
// So, it can only ever be logged between `#1` and `#2` being logged;
// it can sometimes occur more than once, or not at all between `#1` and `#2`
const interval = setInterval( _ => console.log( '#1.5' ), 0 );
(async _ => {
// Using a for loop so this demo doesn't run forever.
// Can use an infinite loop instead
for ( let i = 0; i < 150; i++ ) { // while( true ) {
console.log( '#1 About to yield, ' );
await new Promise( resolve => setTimeout( _ => resolve(), 0 ) ); // yield
console.log( '#2 Done yielding (other callbacks may have run)' );
}
clearInterval( interval );
})();
In node.js use setImmediate instead of setTimeout. Do not use process.nextTick
.
To demo with your timer:
var start = (new Date()).getTime()
var q = 0, x = 1000;
;(async _ => {
while (q < x) {
await new Promise( resolve => setTimeout( _ => resolve(), 0 ) );
q++
}
var end = (new Date).getTime()
console.log('a', end - start)
})();
回答2:
Something like this will work:
var THRESHOLDCOUNT = 10000
var THRESHOLDTIME = 15
function loop(fn) {
var start = Date.now()
var changed = false
var x = 0
while (!changed) {
while (x < THRESHOLDCOUNT) {
x++
fn()
}
var end = Date.now()
changed = end - start > THRESHOLDTIME
}
// make room for external events.
setImmediate(loop)
}
回答3:
If you really need a performant loop, I'd say go with requestAnimationFrame()
, it's highly optimized and pretty easy to use.
https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame
来源:https://stackoverflow.com/questions/50011032/optimal-solution-to-creating-a-run-loop-in-javascript