8 ms·
A tour of JavaScript timers on the web
- jancsika 8y agoSo let's say I have a web assembly binary squirreled away in a web worker or AudioWorklet. 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... :(
- hokus 8y agoThere is a "white man can't dance" joke inthere but I cant think of it right now.
- digitaLandscape 8y agoThe `WorkerGlobalScope` interface extends the `WindowTimers` interface, so you can still use `setTimeout` or `setInterval` in your workers.
- Lerc 8y agoThe communication between worker and Host script is through postmessage. So yes you are just moving the event pump to a onmessage pump. If you have an infinite loop in a worker it will run fine, you just can't ever tell it anything, rendering it somewhat pointless. 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.
- jancsika 8y agoThe only possibility I see currently is to just slave the main loop to the AudioWorklet processing callback and blindly hope that it is able to hit the scheduling deadlines. 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.
- hokus 8y agoI haven't benched it in a while but as far as I know setTimeout and setInterval can have an extra delay depending on [what I call] browser pain. 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)
- jonquark 8y agoFrom the title, I thought this was going to be a collection of Javascript based count down timers like: http://coralbark.net/javascriptTimer/ http://coralbark.net/javascriptTimer/ (It describes ways of timing things in Javascript code).
- decasia 8y agoHa, I independently wrote almost the identical thing (minus bells and whistles) on Friday in a fit of procrastination. So if anyone wants a dark mode for their hacked together javascript timer: https://decasia.org/tools/timer.html https://decasia.org/tools/timer.html
- brianzelip 8y ago{ altTitle: '"whirlwind tour of the various timer functions available in the browser"' }
- Jarred 8y agoReact Native adds an additional timer: InteractionManager.runAfterInteractions[0] 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/interactionmanager https://facebook.github.io/react-native/docs/interactionmana...
- Waterluvian 8y agoMaybe I'm wrong but I always thought settimeout was far simpler than this blog goes on about. My understanding is that it puts a frame into the queue that keeps getting ignored until _at least_ timeout ms has elapsed. 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?
- jayflux 8y agoThe timer basically runs in another thread, and once it’s finished it’s callback/frame goes into the queue, it does not go into the queue before the time you gave. The reason the time can’t be accurate is because other “frames” could be ahead of it in the queue after the timer has finished.
- asknthrow 8y agoI don't think this is correct. AFAIK they both run in the UI thread (which is why running your timer in a webworker turns out to be more accurate as only the callback is affected by queue length of scheduled calls in the UI thread). Although this article https://www.html5rocks.com/en/tutorials/audio/scheduling https://www.html5rocks.com/en/tutorials/audio/scheduling is about sheduling audio specifically, it is the go-to resource for accurate web timing (and although not explicitly mentioned, the metronome is supposed to run in a webworker).
- gsnedders 8y agoIIRC, zero is special cased and does something like jump to the front of the queue. But I've not worked on any of this in years!
- tzs 8y agoWhich is the right one for this situation? 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.
- SimeVidas 8y agoSeveral seconds of JavaScript execution? That sounds like something that would be best performed in a web worker. If you can pass the data to a worker and have it generate the final markup, you won’t have to worry about timers or execution time, since the worker doesn’t block the main thread.
- nfriedly 8y agoIt might be better to use two calls. First call requestAnimationFrame and in the first callback, set the column and make a second rAF or rIC call. In the second callback, do your sorting. 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.
- TheAceOfHearts 8y agoIf the list is REALLY long you should consider sorting it from a web worker. 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?
- acemarke 8y agoFWIW, the React team _just_ published a new package called `schedule` this evening, which acts as a polyfill for `requestIdleCallback` and adds a bunch of additional scheduling capabilities. It's intended to be used with React and its upcoming async rendering capabilities, but appears to be sufficiently standalone to be reused. - Package: https://www.npmjs.com/package/schedule https://www.npmjs.com/package/schedule - Source: https://github.com/facebook/react/tree/master/packages/schedule https://github.com/facebook/react/tree/master/packages/sched...
- dandare 8y agoI don't understand the purpose of setTimeout(/* ... */, 0) (or setImmediate). If I want a function to execute immediately I will call it directly, not via setTimeout(0). What am I missing?
- Doxin 8y agosetTimeout(0) doesn't call the function immediately. It will call the function at the next opportunity. Usually this means that for example that element you added to the DOM has now rendered.
- dandare 8y agoElement added to the DOM is not rendered immediately? 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" }
- tzs 8y agoThat does not appear to be the case in Firefox (62.0). Using dandare's blue button adding function from his reply to your comment here is some test code: 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.