Skip to content

Commit b0f8add

Browse files
committed
doc: README
1 parent bdd6a79 commit b0f8add

3 files changed

Lines changed: 184 additions & 42 deletions

File tree

README.md

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,142 @@
1+
# timerider <a href="https://github.com/denostack"><img src="https://raw.githubusercontent.com/denostack/images/main/logo.svg" width="160" align="right" /></a>
2+
3+
<p>
4+
<a href="https://github.com/denostack/timerider/actions"><img alt="Build" src="https://img.shields.io/github/actions/workflow/status/denostack/timerider/ci.yml?branch=main&logo=github&style=flat-square" /></a>
5+
<a href="https://codecov.io/gh/denostack/timerider"><img alt="Coverage" src="https://img.shields.io/codecov/c/gh/denostack/timerider?style=flat-square" /></a>
6+
<img alt="License" src="https://img.shields.io/npm/l/timerider.svg?style=flat-square" />
7+
<img alt="Language Typescript" src="https://img.shields.io/badge/language-Typescript-007acc.svg?style=flat-square" />
8+
<br />
9+
<a href="https://jsr.io/@denostack/timerider"><img alt="JSR version" src="https://jsr.io/badges/@denostack/timerider?style=flat-square" /></a>
10+
</p>
11+
12+
A robust timer library for Deno that solves common issues with standard `setTimeout` and `setInterval`.
13+
14+
## Features
15+
16+
Timerider improves upon standard timers in three key ways:
17+
18+
1. **Time Drift Correction**: Automatically corrects time drift within 250ms for both `setInterval` and `setTimeout`,
19+
ensuring more accurate timing over long periods.
20+
2. **Long Delay Support**: Handles delays longer than the 32-bit integer limit (`2^31 - 1` ms), which standard timers
21+
cannot process correctly.
22+
3. **Pause & Resume**: Adds the ability to pause a timer and resume it later, perfect for games or interactive
23+
applications.
24+
25+
## Installation
26+
27+
### Deno
28+
29+
```bash
30+
deno add jsr:@denostack/timerider
31+
```
32+
33+
### Node.js & Browser
34+
35+
```bash
36+
npm install timerider
37+
```
38+
39+
## Usage
40+
41+
### Timeout
42+
43+
`createTimeout` works like `setTimeout` but returns a `Timer` object with additional control.
44+
45+
```ts
46+
import { createTimeout } from "@denostack/timerider";
47+
48+
// Basic usage
49+
createTimeout(() => {
50+
console.log("Hello after 1 second");
51+
}, 1000);
52+
53+
// Pause and Resume
54+
const timer = createTimeout(() => {
55+
console.log("This will run eventually...");
56+
}, 5000);
57+
58+
// Pause the timer
59+
timer.pause();
60+
61+
// Resume after some time
62+
setTimeout(() => {
63+
timer.resume(); // Will resume waiting for the remaining time
64+
}, 2000);
65+
```
66+
67+
### Interval
68+
69+
`createInterval` works like `setInterval` but with built-in drift correction.
70+
71+
```ts
72+
import { createInterval } from "@denostack/timerider";
73+
74+
createInterval(() => {
75+
console.log("Tick every 1 second");
76+
}, 1000);
77+
```
78+
79+
### Long Delays
80+
81+
Standard timers fail with delays larger than ~24.8 days (2^31 - 1 milliseconds). Timerider handles this seamlessly.
82+
83+
```ts
84+
import { createTimeout } from "@denostack/timerider";
85+
86+
// Wait for 30 days
87+
const thirtyDays = 1000 * 60 * 60 * 24 * 30;
88+
89+
createTimeout(() => {
90+
console.log("See you next month!");
91+
}, thirtyDays);
92+
```
93+
94+
## API Reference
95+
96+
### `createTimeout(callback, delay)`
97+
98+
Creates a timer that executes the callback after the specified delay.
99+
100+
- `callback`: Function to execute.
101+
- `delay`: Number (ms) or Date object.
102+
- Returns: `Timer`
103+
104+
### `createInterval(callback, interval, delay?)`
105+
106+
Creates a timer that repeatedly executes the callback at the specified interval.
107+
108+
- `callback`: Function to execute.
109+
- `interval`: Number (ms) for the repetition interval.
110+
- `delay`: (Optional) Number (ms) or Date object for the initial start delay.
111+
- Returns: `Timer`
112+
113+
### `Timer` Interface
114+
115+
The object returned by `createTimeout` and `createInterval`.
116+
117+
```ts
118+
interface Timer {
119+
/**
120+
* Returns the current state of the timer.
121+
* "waiting": Timer is running and waiting for the next execution.
122+
* "paused": Timer is paused.
123+
* "completed": Timer has finished (for timeouts) or paused permanently.
124+
*/
125+
state(): "waiting" | "paused" | "completed";
126+
127+
/**
128+
* Pauses the timer. The remaining time is preserved.
129+
*/
130+
pause(): Timer;
131+
132+
/**
133+
* Resumes the timer from where it left off.
134+
*/
135+
resume(): Timer;
136+
137+
/**
138+
* Permanently pauses the timer. Similar to clearTimeout/clearInterval.
139+
*/
140+
clear(): void;
141+
}
142+
```

timer.test.ts

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -41,18 +41,18 @@ Deno.test("createTimeout() - executes callback after delay (Date)", async () =>
4141
assertSpyCalls(spyCallback, 1);
4242
});
4343

44-
Deno.test("createTimeout() - can be stopped and restarted", async () => {
44+
Deno.test("createTimeout() - can be paused and restarted", async () => {
4545
const spyCallback = spy(() => {});
46-
// Start with 100ms delay, but stop immediately
47-
const timer = createTimeout(spyCallback, 100).stop();
48-
assertEquals(timer.state(), "stopped");
46+
// Start with 100ms delay, but pause immediately
47+
const timer = createTimeout(spyCallback, 100).pause();
48+
assertEquals(timer.state(), "paused");
4949

5050
// Wait longer than the delay
5151
await sleep(150);
5252
assertSpyCalls(spyCallback, 0);
5353

5454
// Restart
55-
timer.start();
55+
timer.resume();
5656
assertEquals(timer.state(), "waiting");
5757

5858
// Wait for the remaining time
@@ -113,31 +113,31 @@ Deno.test("createInterval() - executes with initial delay", async () => {
113113
timer.clear();
114114
});
115115

116-
Deno.test("createInterval() - can be stopped and restarted", async () => {
116+
Deno.test("createInterval() - can be paused and restarted", async () => {
117117
const spyCallback = spy(() => {});
118118
const timer = createInterval(spyCallback, 50);
119119

120120
// Allow 2 calls (~100ms)
121121
await sleep(120);
122-
const callsBeforeStop = spyCallback.calls.length;
123-
assertEquals(callsBeforeStop >= 2, true);
122+
const callsBeforePause = spyCallback.calls.length;
123+
assertEquals(callsBeforePause >= 2, true);
124124

125-
// Stop
126-
timer.stop();
127-
assertEquals(timer.state(), "stopped");
125+
// Pause
126+
timer.pause();
127+
assertEquals(timer.state(), "paused");
128128

129129
// Wait 200ms (should be no new calls)
130130
await sleep(200);
131-
assertSpyCalls(spyCallback, callsBeforeStop);
131+
assertSpyCalls(spyCallback, callsBeforePause);
132132

133133
// Restart
134-
timer.start();
134+
timer.resume();
135135

136136
// Wait another 100ms (expect more calls)
137137
await sleep(120);
138138

139139
const callsAfterRestart = spyCallback.calls.length;
140-
assertEquals(callsAfterRestart > callsBeforeStop, true);
140+
assertEquals(callsAfterRestart > callsBeforePause, true);
141141

142142
timer.clear();
143143
});

timer.ts

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ const MAX_DELAY = 2147483647; // 2 ** 31 - 1
44
const ACCURACY = 250;
55

66
export interface Timer {
7-
state(): "waiting" | "stopped" | "completed";
8-
stop(): Timer;
9-
start(): Timer;
7+
state(): "waiting" | "paused" | "completed";
8+
pause(): Timer;
9+
resume(): Timer;
1010
clear(): void;
1111
}
1212

@@ -19,21 +19,21 @@ export function createTimeout(
1919
): Timer {
2020
let waitUntil = parseDelay(delay ?? 0);
2121
let timer: ReturnType<typeof setTimeout> | null = null;
22-
let stopRemains: number | null = null;
22+
let pauseRemains: number | null = null;
2323
let isCompleted = false;
2424

2525
const timeoutInstance = {} as Timer;
26-
function stop() {
26+
function pause() {
2727
if (isCompleted) return timeoutInstance;
2828
timer && clearTimeout(timer);
29-
stopRemains = stopRemains ?? waitUntil - Date.now();
29+
pauseRemains = pauseRemains ?? waitUntil - Date.now();
3030
return timeoutInstance;
3131
}
32-
function start() {
32+
function resume() {
3333
if (isCompleted) return timeoutInstance;
34-
if (typeof stopRemains === "number") {
35-
waitUntil = Date.now() + stopRemains;
36-
stopRemains = null;
34+
if (typeof pauseRemains === "number") {
35+
waitUntil = Date.now() + pauseRemains;
36+
pauseRemains = null;
3737
}
3838
const remains = Math.max(0, waitUntil - Date.now());
3939
if (remains <= ACCURACY) {
@@ -42,7 +42,7 @@ export function createTimeout(
4242
cb();
4343
}, remains);
4444
} else {
45-
timer = setTimeout(start, Math.min(remains >> 1, MAX_DELAY));
45+
timer = setTimeout(resume, Math.min(remains >> 1, MAX_DELAY));
4646
}
4747
return timeoutInstance;
4848
}
@@ -52,16 +52,16 @@ export function createTimeout(
5252
}
5353
function state() {
5454
if (isCompleted) return "completed";
55-
if (typeof stopRemains === "number") return "stopped";
55+
if (typeof pauseRemains === "number") return "paused";
5656
return "waiting";
5757
}
5858

5959
timeoutInstance.state = state;
60-
timeoutInstance.start = start;
61-
timeoutInstance.stop = stop;
60+
timeoutInstance.pause = pause;
61+
timeoutInstance.resume = resume;
6262
timeoutInstance.clear = clear;
6363

64-
start();
64+
resume();
6565

6666
return timeoutInstance;
6767
}
@@ -77,31 +77,31 @@ export function createInterval(
7777
): Timer {
7878
let waitUntil = parseInterval(parseDelay(delay ?? 0), interval ?? 0);
7979
let timer: ReturnType<typeof setTimeout> | null = null;
80-
let stopRemains: number | null = null;
80+
let pauseRemains: number | null = null;
8181
let isCompleted = false;
8282

8383
const intervalInstance = {} as Timer;
84-
function stop() {
84+
function pause() {
8585
if (isCompleted) return intervalInstance;
8686
timer && clearTimeout(timer);
87-
stopRemains = stopRemains ?? waitUntil - Date.now();
87+
pauseRemains = pauseRemains ?? waitUntil - Date.now();
8888
return intervalInstance;
8989
}
90-
function start() {
90+
function resume() {
9191
if (isCompleted) return intervalInstance;
92-
if (typeof stopRemains === "number") {
93-
waitUntil = Date.now() + stopRemains;
94-
stopRemains = null;
92+
if (typeof pauseRemains === "number") {
93+
waitUntil = Date.now() + pauseRemains;
94+
pauseRemains = null;
9595
}
9696
const remains = Math.max(0, waitUntil - Date.now());
9797
if (remains <= ACCURACY) {
9898
timer = setTimeout(() => {
9999
cb();
100100
waitUntil = parseInterval(waitUntil, interval || 0);
101-
start();
101+
resume();
102102
}, remains);
103103
} else {
104-
timer = setTimeout(start, Math.min(remains >> 1, MAX_DELAY));
104+
timer = setTimeout(resume, Math.min(remains >> 1, MAX_DELAY));
105105
}
106106
return intervalInstance;
107107
}
@@ -111,16 +111,16 @@ export function createInterval(
111111
}
112112
function state() {
113113
if (isCompleted) return "completed";
114-
if (typeof stopRemains === "number") return "stopped";
114+
if (typeof pauseRemains === "number") return "paused";
115115
return "waiting";
116116
}
117117

118-
intervalInstance.start = start;
119-
intervalInstance.stop = stop;
118+
intervalInstance.pause = pause;
119+
intervalInstance.resume = resume;
120120
intervalInstance.clear = clear;
121121
intervalInstance.state = state;
122122

123-
start();
123+
resume();
124124

125125
return intervalInstance;
126126
}

0 commit comments

Comments
 (0)