Library / SDK
chrisguttandin/worker-timers avatar
chrisguttandin/worker-timers

worker-timers: setTimeout and setInterval that survive a blurred tab

A replacement for setInterval() and setTimeout() which works in unfocused windows.

646 stars26 forksJavaScriptMIT

At a glance

What is it?
worker-timers replaces the browser's setTimeout and setInterval with versions backed by a Web Worker, so timers keep firing at full frequency when the window loses focus and the browser throttles the main thread. It suits a page with a visible clock or a polling loop, and it is a poor fit for a server-rendered app or an Angular project relying on Zone.js.
Who is it for?
worker-timers fits a browser application whose timing behaviour visibly breaks when the user switches tabs, since the scheduling moves off the main thread and out of reach of the browser's throttling of background windows, and the API is a drop-in import with the same function signatures.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 52 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Why the main thread throttles and workers do not

worker-timers is described as a replacement for setInterval() and setTimeout() that works in unfocused windows, and the motivation section explains exactly which browser behaviour it works around.

Scripts that rely on WindowTimers, the specification that defines setInterval and setTimeout, behave confusingly when the site loses focus. Chrome, Firefox and possibly others throttle the frequency at which those timers are invoked to a maximum of once per second in that situation. A page showing a live clock or polling an endpoint therefore appears to work perfectly while focused and then drops to a crawl the moment the user looks at another tab.

The key observation is that this throttling only applies to the main thread and does not affect the behaviour of Web Workers. So the workaround is to move the scheduling itself into a worker, and that is exactly what worker-timers does.

That is a narrow, specific and very real problem. It is not a general performance fix and it is not a way to make timers more accurate. It is about the case where a timer is expected to fire frequently and the browser has decided that a background page does not deserve the main thread's attention.

The practical consequence is that a page relying on this package is doing something a normal page cannot, and the size of the effect depends entirely on how often the timer is supposed to fire. A timer set for 100 milliseconds that is throttled to 1000 is visibly wrong. A timer set for 30 seconds is barely affected, and the extra machinery is not worth it.

The same API, with one deliberate difference in clearing timers

Installing the package is an npm install, and the usage is a single import:

shell
npm install worker-timers
js
import { clearInterval, clearTimeout, setInterval, setTimeout } from 'worker-timers';

The readme is explicit that the usage is exactly the same as with the corresponding functions on the global scope, despite two documented differences: error handling, and the differentiation between intervals and timeouts.

The second difference is the one that will bite you. The native WindowTimers keep a single list of timers, which means an interval can be cancelled by calling clearTimeout() and vice versa, since everything lives in one list. worker-timers maintains two separate lists internally, so the two kinds of timer cannot be cancelled with each other's function:

js
const periodicWork = () => {};

// This will stop the interval.
const windowId = window.setInterval(periodicWork, 100);
window.clearTimeout(windowId);

// This will not cancel the interval. It may cancel a timeout.
const workerId = setInterval(periodicWork, 100);
clearTimeout(workerId);

So a codebase that relies on the sloppy interchangeability of the built-ins, whether by accident or by copying old code, changes behaviour when it swaps in this package. An interval that was previously cancellable by clearTimeout will now keep running until the page unloads or something calls clearInterval. That is a genuine migration hazard, and it is the sort of thing that turns into a memory leak in a long-lived dashboard rather than an exception.

The dependency list explains the architecture. The package depends on worker-timers-broker and worker-timers-worker alongside tslib and the Babel runtime, so the scheduling logic lives in a separate worker package and the broker is the piece that the main thread talks to.

Server-side rendering has no fallback, on purpose

The package is intended for the browser and requires Web Workers support. It does not contain a fallback that would let it run in another environment such as Node.js, which knows nothing about Web Workers, and the readme explains the reasoning: it is to prevent the package from silently failing in an unsupported browser.

That reasoning is defensible and the cost is real. A silent failure in an unsupported browser would be worse than a hard error, because a timer that silently stops firing is a bug that surfaces far from its cause. Failing loudly at import time is the better failure mode.

The cost is that worker-timers has to be replaced in any project that also does server-side rendering. The readme says the replacement should be straightforward at least in theory, because each function has exactly the same signature as its corresponding builtin. In practice, it notes, the configuration of a real-life project can be tricky, and points to a concrete example, worker-timers-ssr-example, provided by a contributor, showing the usage inside a server-side rendered React app.

That example being a separate repository rather than part of this one is itself informative. SSR integration is not a solved problem inside the package; it is a problem the user has to solve, with the project pointing at someone else's worked example. For a team with a Next.js or Remix application, this is the first thing to investigate, because the import has to be conditional and the bundler configuration has to keep the worker code out of the server bundle.

Zone.js patches the native timers and never sees the worker ones

The Angular section describes a problem worth understanding, because it is not specific to this package and it will show up in any framework that instruments the built-in timer functions.

Angular uses Zone.js, which is the default, to detect changes. Zone.js patches the native setInterval() and setTimeout() functions so that it gets notified about the invocation of their callbacks, and that notification is what triggers Angular's change detection.

Zone.js is not aware of worker-timers, so it does not get notified about any callback invocations. The consequence is that state changed inside a callback scheduled through worker-timers does not trigger change detection, and Angular has to be notified manually about those state changes.

That is the trade in one sentence. You get timers that are not throttled, and you take on the responsibility of triggering change detection yourself. In an application where most updates flow through Zone.js, mixing the two means some updates are automatic and some are not, which is exactly the kind of inconsistency that produces a bug where a value updates on screen only after some unrelated interaction.

The same reasoning applies to any other library that patches the global timers, and to any framework that assumes all scheduled callbacks go through the patched versions. The readme is doing something valuable here by documenting the interaction rather than leaving it to be discovered in production.

Where worker-timers is the wrong choice

The first limitation is that it only solves the throttling problem, and only in a background tab. If your timer is misbehaving for a different reason, such as a long synchronous task blocking the main thread, or a callback that takes longer than the interval, this package will not help. The underlying cause is that the browser is refusing to give the page the attention it asked for, and the worker approach only sidesteps the part of that which is throttling.

The second is the cost of the worker. Each timer needs a scheduling decision that crosses a boundary between the page and the worker, and the package's own dependency list shows that this is a real mechanism with a broker, a worker and a runtime rather than a few lines of code. For a page with hundreds of timers, or with a timer firing every few milliseconds, the messaging overhead is not obviously cheaper than the thing it replaces. The package is aimed at scripts that need a sub-second cadence, but the readme does not document a threshold at which the overhead becomes a problem.

The third is the interval and timeout asymmetry, already described. A codebase that clears timers with the wrong function will behave differently, and the difference is silent.

The fourth is the environment ceiling. No Web Workers means no worker-timers, and the package refuses to pretend otherwise. Node.js, a server render, a web worker environment itself, or a browser without worker support all fall outside what it can do, and a project that has to run in several of those is signing up for a conditional import and a build configuration exercise.

The fifth is the maintenance picture. The last push was on 2026-09-01, and the release history shows a steady patch cadence with v8.0.34 on 2026-07-21, v8.0.33 on 2026-06-23 and v8.0.32 on 2026-06-15. There is a husky directory, a lint-staged setup, commitizen for conventional commit messages and a babel and rollup toolchain with prettier, eslint and vitest including a browser webdriver mode, so the project is clearly maintained with care. The risk is not neglect but the usual one for a small utility: it depends on the browser keeping its throttling behaviour, and if that behaviour changes, the reason the package exists quietly stops applying.

The sixth is the security contact arrangement, which is unusual for a package of this size. Vulnerabilities are reported through the Tidelift security contact and Tidelift coordinates the fix and the disclosure, which tells you the project has commercial sponsorship arrangements around its security process, and that a security report goes to a company rather than to a volunteer maintainer.

Editorial conclusion

worker-timers fits a browser application whose timing behaviour visibly breaks when the user switches tabs, since the scheduling moves off the main thread and out of reach of the browser's throttling of background windows, and the API is a drop-in import with the same function signatures. It is the wrong choice for a server-rendered application, because the package has no fallback for environments without Web Workers and must be swapped out, and it is the wrong choice for an Angular project on Zone.js, which will not be notified of the callbacks. Before adopting it, read the error handling and interval-versus-timeout sections, and note that the last push was on 2026-09-01 with version 8.0.34 published on 2026-07-21.

Frequently asked questions

How do I install worker-timers?

It is published on npm as worker-timers, so npm install worker-timers is the whole installation. You then import the exported functions with import { clearInterval, clearTimeout, setInterval, setTimeout } from 'worker-timers'.

What problem does worker-timers solve?

Browsers throttle the main thread's setInterval and setTimeout to at most one invocation per second when the window is unfocused. Web Workers are not throttled in the same way, so worker-timers moves the scheduling into a worker to keep the timer running at its intended frequency in a background tab.

Can I cancel an interval with clearTimeout in worker-timers?

No. The native WindowTimers keep one list of timers so the two clearing functions are interchangeable, but worker-timers keeps separate lists for intervals and timeouts, so clearTimeout on an interval will not cancel it and may cancel a timeout instead.

Does worker-timers work with Angular and Zone.js?

It works, but you have to notify Angular manually. Zone.js patches the native setInterval and setTimeout to detect change detection triggers and is not aware of worker-timers, so state changes inside a worker-timers callback are not picked up automatically.

Can worker-timers be used in a server-side rendered application?

Not directly. The package requires Web Workers and has no fallback for environments without them, which is deliberate so it fails loudly rather than silently. It has to be replaced in server-rendered projects, and the project points to a separate worker-timers-ssr-example showing the usage in a server-side rendered React app.

Official sources

  1. chrisguttandin/worker-timers on GitHub
  2. Issues
  3. License: MIT
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/chrisguttandin-worker-timers.svg)](https://hysenlabs.com/projects/chrisguttandin-worker-timers)

Was this useful?