A tour of JavaScript timers on the web
nolanlawson.com
nolanlawson.com
(It describes ways of timing things in Javascript code).
So if anyone wants a dark mode for their hacked together javascript timer:
It's useful for running code after an animation has completed, to make it easier for animations to be smooth. Would be cool for something like this to be added into JS proper (particularly if CSS or JS animations are not hardware-accelerated)
[0]: https://facebook.github.io/react-native/docs/interactionmana...
I measure this delay to set the pace of computationally heavy tasks.
I also consider it a proof of concept why browsers should expose more of the performance and capability stats to the web page. Maybe I don't want translucent layers of css animations if it freezes the gui? At least, I think I don't.
Also funny:
Because callbacks can trigger computationally heavy tasks that may trigger at the same time I've morphed into the habit of just slapping the data into an array and parsing it with a setTimeout used as a setInterval.
So, basically I went back to polling because of superior performance. (ha-ha)
Therefore, settimeout of 0 is to basically place a frame into the queue, "do this once the stack is empty and there's nothing in front of it in the queue."
They're just about placing frames into some place in the queue.
Is it not that simple?
If that binary is supposed to have its own event loop, how do I achieve that? Seems like I'd have to hook back into the JS event loop, at which point my poor binary becomes subject to all the DOM blockers I was trying to avoid in the first place... :(
What is really needed is the ability to preempt/resume javascript in workers.
You could fudge that with a preprocessor script that inserted, at judicious points, polling of a pseudo-interrupt flag. You'd have to use a SharedArrayBuffer which you can't have because it got disabled post-spectre.
Unfortunately, I fear any mechanism that works properly would let you do things precisely and well and would consequently be blocked as a possible avenue for spectre.
The current native app (say, in Linux) has both a scripting language and a DSP "dataflowy" language that can run and interact in the same process. This means the user can manually (or programmatically) turn on DSP processing opportunistically. However, with the Web Audio idea above this means that the software I'm porting must be calculating its DSP graph from the AudioWorklet callback at all times for the software to work at all. If I suspend audio computation in the parent script time would no longer move forward. Or I'd have to switch to having the DOM poll the worker-- feeding it the current time-- which I'm just guessing would kill performance.
I want to sort a table with a large number of rows after a user clicks on a column heading. This will take several seconds, so I want to change the column header to a bold, red "Sorting...please wait" while the sort is taking place, then change it back.
After changing the heading, I need to put off calling the sort function until after the heading change is actually rendered for the user.
The animation one sounds promising...but from the description it sounds to me like it runs just before rendering, and I want after rendering.
The idle one also sounds good in that it sounds like the rendering will be done before it runs...but if other stuff is going on it may take a long time for idle to actually happen.
What I've used in the past was setTimeout with a value of 0, because that's what Stackoverflow said to do.
Also, it should be possible to do the sorting in a way that mostly doesn't block the browser. The data itself (if it lives outside of the DOM) can be sorted in a web worker. The DOM obviously blocks the UI thread, but you could do small batches of changes with a rAF or a rIC in-between each.
Or, you know, just stick with setTimeout(..., 0) since that's already working for you.
You might also want to consider implementing windowing or using a library, that way you only render what's visible on-screen. However this has its own caveats, so I'd suggest considering it carefully. For example, it'll break browser search for non-visible items. If you can get away with it I'd definitely suggest sticking with a traditional <table> element.
Can we get an idea of the size of this list?
There was an internal tool written in PHP that that put up a form for customer support people to enter information about transactions they were interested in, then queried the customer database to find matching transaction. It then output a page with the filled-in search form and a table with the results. There was no JavaScript used on that page. Just a simple form and the table.
It didn't happen often, but occasionally the table could have many thousands of rows.
The project to add sorting was one of those "it would be nice if we could sort this...I bet there's code on Stackoverflow that I can just grab and drop in between things I'm actually supposed to be working on today!" things.
- Package: https://www.npmjs.com/package/schedule
- Source: https://github.com/facebook/react/tree/master/packages/sched...
for instance, this works as expected, the button will be blue:
function myFunction() { var btn = document.createElement("BUTTON"); var t = document.createTextNode("CLICK ME"); btn.appendChild(t); document.body.appendChild(btn); btn.style.color = "blue" }
function myFunction()
{
var btn = document.createElement("BUTTON");
var t = document.createTextNode("CLICK ME");
btn.appendChild(t);
document.body.appendChild(btn);
btn.style.color = "blue";
}
function slow(n)
{
console.log('slow called');
let s = 0;
for (let i = 0; i < n; ++i)
s += 1.00000001;
console.log('slow:' + s);
return s;
}
function callMyFunction(to)
{
myFunction();
setTimeout(function () {slow(5000000000);}, to);
}
The argument to slow() in the timeout function, 5000000000, causes it to take about 5 seconds on my system, long enough to easily tell if the button has rendered before slow runs.In Chrome, callMyFunction(0) results in the blue button showing up, then 'slow called' in the log, then 5 seconds later slow() finishes. I did this several times and the results were the same every time.
In Firefox, it results in 'slow called' in the log, then 5 seconds later slow finishes and the blue button shows up. Several tries with callMyFunction(0) resulted in no instances of the rendering being done before the timeout function was called.
I had to bump the argument to callMyFunction up to 35 to get the rendering to happen consistently before the timeout function (10 out of 10 times). It mostly completed before the timeout at 34, but not always.
This is surprising to me, because the impression I got when I looked into this a few years ago on Stackoverflow and other web sites was the setTimeout(0) was a reliable way to make sure that all DOM changes were rendered.
My test procedure: open browser. Open inspector. Paste the above code into the console. invoke callMyFunction from the console while watching the screen for the button and the log for slow's messages. I wonder if JavaScript invoked from the console might have some different rules for rendering timing?
Edit: nope, it has nothing to do with the console. I tried the above, but invoked by a click on a page instead of from the console, and the results are the same.
1. Make a visible change to the DOM,
2. wait somehow for that hopefully to become visible,
3. call a slow compute task
I tried the following methods for waiting:
1. setTimeout (various values)
2. window.requestAnimationFrame
3. windows.requestIdleCallback
4, 5, 6. doubled forms of the above three methods. By doubled I mean that instead of, say, calling the slow function from the requestAnimationFrame callback, that callback does another requestAnimationFrame, and it is the callback for that second requestAnimationFrame.
Here is a test program that puts up a page with a count on top, a couple fields to give the timeout for setTimeout and how slow the slow task should be, and buttons to try the aforementioned delay methods (also one to call direct without a delay). Clicking the button will increment the counter, invoke the delay, and then invoke the slow function. It also prints timing information on when various events happened and when various things were called (overwritten for each test), and also a log below that of events and slow task timing.
Results follow.
=== Firefox 62.0 (Mac, Windows)
Double requestAnimationFrame and double requestIdleCallback seem to be reliable. I've never seen the slow task start before the counter display has updated.
Single requestAnimationFrame and requestIdleCallback usually run the slow task before the counter display has updated, but every so often the display updates first. On Windows, single requestIdleCallback seems more likely to do the update first than on Mac.
setTimeout(0) seems to always runs the slow task before the display updates.
Double setTimeout(0) is similar to single requestAnimationFrame or requestIdleCallback. Mostly does not update until after the slow task, but occasionally does update first.
=== Chrome 68.0.whatever_is_current (Mac)
setTimeout(0) seems to always update display before running slow task.
Single requestIdleCallback, same as setTimeout(0).
Single requestAnimationFrame seems to always run the slow task before display update.
Double requestAnimationFrame seems to always update the display before the slow task.
=== Safari (Mac)
setTimeout(0) and single requestAnimationFrame update display before slow task. Does not have requestIdleCallback.
=== Edge (Windows)
No requestIdleCallback.
setTimeout(0) and single requestAnimationFrame seem to always update display before slow task runs.
Looks like double requestAnimationFrame is the way to go.
e.g.
function main() {
// do animation stuff
main();
}
Will throw an error. If you do the following: function main() {
// do animation stuff
window.setTimeout(main, 0);
}
You wont get the error, because main() is being called by window instead, so is not recursive.(window.requestAnimationFrame is usually better for animation though).