4 Creative Ways to JavaScript Timing in Browsers

Time Photo by Lukas Blazek on Unsplash

This article assumes the availability for Web APIs, therefore, most methods suggested here don’t work in NodeJS.

Using an infinite synchronous loop in a Web Worker (not Service Worker)

Since Web Workers are essentially web threads, you can infinitely loop inside them without blocking the main thread. This gives you access to sub-millisecond time resolutions. This is especially good to make time critical decisions within the worker, and let the main thread know when (accurately) you see fit. Eg. Rerender something whenever microseconds are a prime number. To get access to microseconds, you can use performance.now.

Pros and cons

Pros
  1. Sub-millisecond resolution.
  2. Near zero cost on the UI thread.
  3. Fully asynchronous from a UI thread perspective. Thanks to Web Workers messaging design.
  4. Safe ending. Unlike setInterval, calling worker.terminate guarantees no more messages will be received. Quoting MDN, “The terminate() method of the Worker interface immediately terminates the Worker. This does not offer the worker an opportunity to finish its operations; it is simply stopped at once.”
Cons
  1. Even though you can make sub-millisecond decisions, messaging back to the UI thread is asynchronous. You can’t render as timely as you make the decision in the worker.
  2. Keeps a thread fully occupied. Might be heavy on phone batteries.
  3. Requires Web Worker support.
  4. Doesn’t pause when the tab isn’t focused.

Codesandbox example

Using CSS animations for time events (particularly animationiteration)

>

If you create a div (don’t use a div though, see point 2 of cons) with an infinite animation. You can subscribe to its animationiteration event and get notified whenever animation-duration passes.

Pros
  1. Automatic suspension when the tab isn’t in focus. The event doesn’t fire at all when the tab isn’t in focus. You don’t need to worry about jammed calls that will all run at once when the tab is in focus again.
  2. Automatic clean up when the your hidden div is removed from the DOM. For example, if you have a React component that renders the time, you don’t need to do anything on unmounting. The div will be removed and the event will not fire anymore.
  3. This is subjective but the subscription logic is beautiful. Eg. .addEventListener("animationiteration", fun).
  4. Super clean way to delay the start of the timer by using animation-delay.
Cons
  1. A little too clever, might confuse your teammates/contributors.
  2. Depends on the DOM and CSSOM. Other CSS rules can interfere with yours. That’s why I suggest to create an arbitrary non-existent tag like <just-a-timer-element></<just-a-timer-element>. Maybe create a custom element with the CSS animation code neatly tucked within? 🤯 (don’t come after me, kthx).
  3. Doesn’t work if the element has display: none;.
  4. Inaccurate. According to my testing, it can be off by a full 1ms. You can try yourself in the example CSB below.

Codesandbox example

Using SVG tag (SMIL Animations)

Consider

<svg>
  <rect>
    <animate
      attributeName="rx"
      values="0;1"
      dur="1s"
      repeatCount="indefinite"
    />
  </rect>
</svg>

If you do animate.addEventListener('repeat', fun), your function will be called every dur (1s) period.

Pros
  1. Works even when the SVG is display: none;.
  2. Stops automatically when the SVG is removed from the DOM.
  3. Doesn’t begin until full page load.
  4. Auto-pauses when the tab isn’t in focus.
Cons
  1. A little too clever, might confuse your teammates/contributors.
  2. Depends on the DOM and CSSOM. Same caveats as above. Other CSS rules can interfere with your configuration.
  3. Isn’t supported in IE and Edge (before Chromium).
  4. Inaccurate. According to my testing, it can be off by a whopping 15ms. You can try yourself in the example CSB below.
  5. Doesn’t begin until full page load. Yup, can be a bug and a feature.

Codesandbox example

Using Web Animations API

Web Animations API allows you to animate DOM elements from within JavaScript.

Interestingly, you can animate unmounted elements! This gives you access to a timing mechanism in pure JS (and Web APIs).

Here is an alternative implementation of setTimeout

function ownSetTimeout(callback, duration) {
  const div = document.createElement('div');

  const keyframes = new KeyframeEffect(div, [], { duration, iterations: 1 });

  const animation = new Animation(
    keyframes,
    document.timeline
  );

  animation.play();

  animation.addEventListener('finish', () => {
    callback();
  });
}

Neat, isn’t it?

Pros
  1. Atomic. Doesn’t need DOM interactions.
  2. Easy to understand by an unfamiliar person.
  3. Pauses when the tab isn’t focused.
Cons
  1. Still a proposal. Don’t use in production.
  2. Dire support situation. Probably only works in Chromium.
  3. Still a little counterintuitive.
  4. Pauses when the tab isn’t focused. Can be bad if used as an alternative for setTimeout.
  5. Can’t be used for intervals. Only onfinish event is available.
  6. Inaccurate. According to my testing, ±5ms can happen easily.

Codesandbox example

Bonus

Using Web Audio API to get access to time. This is another great way to have access to accurate intervals and delays. It’s explained in detail in this great article by Chris Wilson (@cwilso). Thanks @0ndras for the tip!

This was fun

I realize this is a niche article. But I felt compelled to write it because I always thought setTimeout and setInterval were the only paths towards asynchronous delays and intervals. But turns out there are a few other options! And who knows, someone might have some strange constraints and might find this useful.

Written on February 14, 2020