首次提交
This commit is contained in:
+21
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 Christoph Guttandin
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
+71
@@ -0,0 +1,71 @@
|
||||

|
||||
|
||||
# worker-timers
|
||||
|
||||
**A replacement for setInterval() and setTimeout() which works in unfocused windows.**
|
||||
|
||||
[](https://www.npmjs.com/package/worker-timers)
|
||||
|
||||
## Motivation
|
||||
|
||||
For scripts that rely on [WindowTimers](http://www.w3.org/TR/html5/webappapis.html#timers) like `setInterval()` or `setTimeout()` things get confusing when the site which the script is running on loses focus. Chrome, Firefox and maybe others throttle the frequency at which they invoke those timers to a maximum of once per second in such a situation. However this is only true for the main thread and does not affect the behavior of [Web Workers](http://www.w3.org/TR/workers/). Therefore it is possible to avoid the throttling by using a worker to do the actual scheduling. This is exactly what `worker-timers` does.
|
||||
|
||||
## Getting Started
|
||||
|
||||
`worker-timers` is available as a package on [npm](https://www.npmjs.org/package/worker-timers). Run the following command to install it:
|
||||
|
||||
```shell
|
||||
npm install worker-timers
|
||||
```
|
||||
|
||||
You can then import the exported functions in your code like this:
|
||||
|
||||
```js
|
||||
import { clearInterval, clearTimeout, setInterval, setTimeout } from 'worker-timers';
|
||||
```
|
||||
|
||||
The usage is exactly the same (despite of the [error handling](#error-handling) and the
|
||||
[differentiation between intervals and timeouts](#differentiation-between-intervals-and-timeouts))
|
||||
as with the corresponding functions on the global scope.
|
||||
|
||||
```js
|
||||
const intervalId = setInterval(() => {
|
||||
// do something many times
|
||||
}, 100);
|
||||
|
||||
clearInterval(intervalId);
|
||||
|
||||
const timeoutId = setTimeout(() => {
|
||||
// do something once
|
||||
}, 100);
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
```
|
||||
|
||||
## Differentiation between Intervals and Timeouts
|
||||
|
||||
The native WindowTimers only maintain a single list of timers. But `worker-timers` maintains two separate lists to store the ids of intervals and timeouts internally. WindowTimers allows intervals to be cancelled by calling `clearTimeout()` and the other way round because it stores all timers in a single list. This is not possible with `worker-timers`.
|
||||
|
||||
```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);
|
||||
```
|
||||
|
||||
## Server-Side Rendering
|
||||
|
||||
This package is intended to be used in the browser and requires the browser to have [support for Web Workers](https://caniuse.com/#feat=webworkers). It does not contain any fallback which would allow it to run in another environment like Node.js which doesn't know about Web Workers. This is to prevent this package from silently failing in an unsupported browser. But it also means that it needs to be replaced when used in a web project which also supports server-side rendering. The replacement should be straightforward, at least in theory, because each function has the exact same signature as its corresponding builtin function. But the configuration of a real-life project can be tricky. For a concrete example, please have a look at the [worker-timers-ssr-example](https://github.com/newyork-anthonyng/worker-timers-ssr-example) provided by [@newyork-anthonyng](https://github.com/newyork-anthonyng). It shows the usage inside of a server-side rendered React app.
|
||||
|
||||
## Angular (& Zone.js)
|
||||
|
||||
If `worker-timers` is used inside of an Angular app and Zone.js (which is the default) is used to detect changes, the behavior of `worker-timers` can be confusing. Angular is using Zone.js which is patching the native `setInterval()` and `setTimeout()` functions to get notified about the invocation of their callback functions. But Angular (more specifically Zone.js) is not aware of `worker-timers` and doesn't get notified about any callback invocations. Therefore Angular needs to be notified manually about state changes that occur inside of a callback function which was scheduled with the help of `worker-timers`.
|
||||
|
||||
## Security contact information
|
||||
|
||||
To report a security vulnerability, please use the [Tidelift security contact](https://tidelift.com/security). Tidelift will coordinate the fix and disclosure.
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
{
|
||||
"author": "Christoph Guttandin",
|
||||
"bugs": {
|
||||
"url": "https://github.com/chrisguttandin/worker-timers/issues"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "cz-conventional-changelog"
|
||||
}
|
||||
},
|
||||
"contributors": [
|
||||
{
|
||||
"email": "a-anng@expedia.com",
|
||||
"name": "Anthony Ng"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.29.2",
|
||||
"tslib": "^2.8.1",
|
||||
"worker-timers-broker": "^8.0.16",
|
||||
"worker-timers-worker": "^9.0.14"
|
||||
},
|
||||
"description": "A replacement for setInterval() and setTimeout() which works in unfocused windows.",
|
||||
"devDependencies": {
|
||||
"@babel/cli": "^7.28.6",
|
||||
"@babel/core": "^7.29.0",
|
||||
"@babel/plugin-external-helpers": "^7.27.1",
|
||||
"@babel/plugin-transform-runtime": "^7.29.0",
|
||||
"@babel/preset-env": "^7.29.2",
|
||||
"@babel/register": "^7.28.6",
|
||||
"@commitlint/cli": "^20.5.0",
|
||||
"@commitlint/config-angular": "^20.5.0",
|
||||
"@rollup/plugin-babel": "^7.0.0",
|
||||
"@rollup/plugin-replace": "^6.0.3",
|
||||
"@vitest/browser-webdriverio": "^4.0.18",
|
||||
"babel-loader": "^10.1.1",
|
||||
"commitizen": "^4.3.1",
|
||||
"cz-conventional-changelog": "^3.3.0",
|
||||
"eslint": "^8.57.0",
|
||||
"eslint-config-holy-grail": "^61.0.11",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^16.4.0",
|
||||
"memfs": "^4.56.11",
|
||||
"prettier": "^3.8.1",
|
||||
"rimraf": "^6.1.3",
|
||||
"rollup": "^4.59.0",
|
||||
"terser-webpack-plugin": "^5.4.0",
|
||||
"tsconfig-holy-grail": "^15.0.3",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-holy-grail": "^56.0.7",
|
||||
"typescript": "^5.9.3",
|
||||
"vitest": "^4.0.18",
|
||||
"webpack": "^5.105.4",
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"files": [
|
||||
"build/es2019/",
|
||||
"build/es5/",
|
||||
"src/"
|
||||
],
|
||||
"homepage": "https://github.com/chrisguttandin/worker-timers",
|
||||
"keywords": [
|
||||
"Web Workers",
|
||||
"WindowTimers",
|
||||
"clearInterval",
|
||||
"clearTimeout",
|
||||
"interval",
|
||||
"setInterval",
|
||||
"setTimeout"
|
||||
],
|
||||
"license": "MIT",
|
||||
"main": "build/es5/bundle.js",
|
||||
"module": "build/es2019/module.js",
|
||||
"name": "worker-timers",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/chrisguttandin/worker-timers.git"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "rimraf build/* && webpack --config config/webpack/worker-es2019.js && tsc --project src/tsconfig.json && rollup --config config/rollup/bundle.mjs && babel ./build/es2019 --config-file ./config/babel/build.json --out-dir ./build/node",
|
||||
"lint": "npm run lint:config && npm run lint:src && npm run lint:test",
|
||||
"lint:config": "eslint --config config/eslint/config.json --ext .cjs --ext .js --ext .mjs --report-unused-disable-directives config/",
|
||||
"lint:src": "tslint --config config/tslint/src.json --project src/tsconfig.json src/*.ts src/**/*.ts",
|
||||
"lint:test": "eslint --config config/eslint/test.json --ext .js --report-unused-disable-directives test/",
|
||||
"prepare": "husky",
|
||||
"prepublishOnly": "npm run build",
|
||||
"test": "npm run lint && npm run build && npm run test:integration-browser && npm run test:integration-node",
|
||||
"test:integration-browser": "if [ \"$TYPE\" = \"\" -o \"$TYPE\" = \"integration\" ] && [ \"$TARGET\" = \"\" -o \"$TARGET\" = \"chrome\" -o \"$TARGET\" = \"firefox\" -o \"$TARGET\" = \"safari\" ]; then npx vitest --config config/vitest/integration.ts; fi",
|
||||
"test:integration-node": "if [ \"$TYPE\" = \"\" -o \"$TYPE\" = \"integration\" ] && [ \"$TARGET\" = \"\" -o \"$TARGET\" = \"node\" ]; then npx vitest --browser.enabled false --config config/vitest/integration.ts; fi"
|
||||
},
|
||||
"types": "build/es2019/module.d.ts",
|
||||
"version": "8.0.31"
|
||||
}
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export const createLoadOrReturnBroker = <Broker>(loadBroker: (url: string) => Broker, worker: string) => {
|
||||
let broker: null | Broker = null;
|
||||
|
||||
return () => {
|
||||
if (broker !== null) {
|
||||
return broker;
|
||||
}
|
||||
|
||||
const blob = new Blob([worker], { type: 'application/javascript; charset=utf-8' });
|
||||
const url = URL.createObjectURL(blob);
|
||||
|
||||
broker = loadBroker(url);
|
||||
|
||||
// Bug #1: Edge up until v18 didn't like the URL to be revoked directly.
|
||||
setTimeout(() => URL.revokeObjectURL(url));
|
||||
|
||||
return broker;
|
||||
};
|
||||
};
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import { load } from 'worker-timers-broker';
|
||||
import { createLoadOrReturnBroker } from './factories/load-or-return-broker';
|
||||
import { worker } from './worker/worker';
|
||||
|
||||
const loadOrReturnBroker = createLoadOrReturnBroker(load, worker);
|
||||
|
||||
export const clearInterval: ReturnType<typeof load>['clearInterval'] = (timerId) => loadOrReturnBroker().clearInterval(timerId);
|
||||
|
||||
export const clearTimeout: ReturnType<typeof load>['clearTimeout'] = (timerId) => loadOrReturnBroker().clearTimeout(timerId);
|
||||
|
||||
export const setInterval: ReturnType<typeof load>['setInterval'] = (...args) => loadOrReturnBroker().setInterval(...args);
|
||||
|
||||
export const setTimeout: ReturnType<typeof load>['setTimeout'] = (...args) => loadOrReturnBroker().setTimeout(...args);
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"isolatedModules": true
|
||||
},
|
||||
"extends": "tsconfig-holy-grail/src/tsconfig-browser"
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// This is the minified and stringified code of the worker-timers-worker package.
|
||||
export const worker = `(()=>{var e={455(e,t){!function(e){"use strict";var t=function(e){return function(t){var r=e(t);return t.add(r),r}},r=function(e){return function(t,r){return e.set(t,r),r}},n=void 0===Number.MAX_SAFE_INTEGER?9007199254740991:Number.MAX_SAFE_INTEGER,o=536870912,s=2*o,a=function(e,t){return function(r){var a=t.get(r),i=void 0===a?r.size:a<s?a+1:0;if(!r.has(i))return e(r,i);if(r.size<o){for(;r.has(i);)i=Math.floor(Math.random()*s);return e(r,i)}if(r.size>n)throw new Error("Congratulations, you created a collection of unique numbers which uses all available integers!");for(;r.has(i);)i=Math.floor(Math.random()*n);return e(r,i)}},i=new WeakMap,u=r(i),c=a(u,i),l=t(c);e.addUniqueNumber=l,e.generateUniqueNumber=c}(t)}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var s=t[n]={exports:{}};return e[n].call(s.exports,s,s.exports,r),s.exports}(()=>{"use strict";const e=-32603,t=-32602,n=-32601,o=(e,t)=>Object.assign(new Error(e),{status:t}),s=t=>o('The handler of the method called "'.concat(t,'" returned an unexpected result.'),e),a=(t,r)=>async({data:{id:a,method:i,params:u}})=>{const c=r[i];try{if(void 0===c)throw(e=>o('The requested method called "'.concat(e,'" is not supported.'),n))(i);const r=void 0===u?c():c(u);if(void 0===r)throw(t=>o('The handler of the method called "'.concat(t,'" returned no required result.'),e))(i);const l=r instanceof Promise?await r:r;if(null===a){if(void 0!==l.result)throw s(i)}else{if(void 0===l.result)throw s(i);const{result:e,transferables:r=[]}=l;t.postMessage({id:a,result:e},r)}}catch(e){const{message:r,status:n=-32603}=e;t.postMessage({error:{code:n,message:r},id:a})}};var i=r(455);const u=new Map,c=(e,r,n)=>({...r,connect:({port:t})=>{t.start();const n=e(t,r),o=(0,i.generateUniqueNumber)(u);return u.set(o,()=>{n(),t.close(),u.delete(o)}),{result:o}},disconnect:({portId:e})=>{const r=u.get(e);if(void 0===r)throw(e=>o('The specified parameter called "portId" with the given value "'.concat(e,'" does not identify a port connected to this worker.'),t))(e);return r(),{result:null}},isSupported:async()=>{if(await new Promise(e=>{const t=new ArrayBuffer(0),{port1:r,port2:n}=new MessageChannel;r.onmessage=({data:t})=>e(null!==t),n.postMessage(t,[t])})){const e=n();return{result:e instanceof Promise?await e:e}}return{result:!1}}}),l=(e,t,r=()=>!0)=>{const n=c(l,t,r),o=a(e,n);return e.addEventListener("message",o),()=>e.removeEventListener("message",o)},d=(e,t)=>r=>{const n=t.get(r);if(void 0===n)return Promise.resolve(!1);const[o,s]=n;return e(o),t.delete(r),s(!1),Promise.resolve(!0)},m=(e,t,r,n)=>(o,s,a)=>{const i=o+s-t.timeOrigin,u=i-t.now();return new Promise(t=>{e.set(a,[r(n,u,i,e,t,a),t])})},f=new Map,h=d(globalThis.clearTimeout,f),p=new Map,v=d(globalThis.clearTimeout,p),w=((e,t)=>{const r=(n,o,s,a)=>{const i=n-e.now();i>0?o.set(a,[t(r,i,n,o,s,a),s]):(o.delete(a),s(!0))};return r})(performance,globalThis.setTimeout),g=m(f,performance,globalThis.setTimeout,w),T=m(p,performance,globalThis.setTimeout,w);l(self,{clear:async({timerId:e,timerType:t})=>({result:await("interval"===t?h(e):v(e))}),set:async({delay:e,now:t,timerId:r,timerType:n})=>({result:await("interval"===n?g:T)(e,t,r)})})})()})();`; // tslint:disable-line:max-line-length
|
||||
Reference in New Issue
Block a user