Skip to content

Commit b2edac7

Browse files
committed
Require Node.js 16 and move to ESM
1 parent 584ad84 commit b2edac7

File tree

7 files changed

+272
-353
lines changed

7 files changed

+272
-353
lines changed

.github/workflows/main.yml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,12 @@ jobs:
1010
fail-fast: false
1111
matrix:
1212
node-version:
13-
- 14
14-
- 12
15-
- 10
13+
- 20
14+
- 18
15+
- 16
1616
steps:
17-
- uses: actions/checkout@v2
18-
- uses: actions/setup-node@v1
17+
- uses: actions/checkout@v3
18+
- uses: actions/setup-node@v3
1919
with:
2020
node-version: ${{ matrix.node-version }}
2121
- run: npm install

index.d.ts

Lines changed: 108 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,125 @@
1-
declare namespace delay {
2-
interface ClearablePromise<T> extends Promise<T> {
3-
/**
4-
Clears the delay and settles the promise.
5-
*/
6-
clear(): void;
7-
}
8-
1+
export type Options<T> = {
92
/**
10-
Minimal subset of `AbortSignal` that delay will use if passed.
11-
This avoids a dependency on dom.d.ts.
12-
The dom.d.ts `AbortSignal` is compatible with this one.
13-
*/
14-
interface AbortSignal {
15-
readonly aborted: boolean;
16-
addEventListener(
17-
type: 'abort',
18-
listener: () => void,
19-
options?: {once?: boolean}
20-
): void;
21-
removeEventListener(type: 'abort', listener: () => void): void;
22-
}
3+
A value to resolve in the returned promise.
234
24-
interface Options {
25-
/**
26-
An optional AbortSignal to abort the delay.
27-
If aborted, the Promise will be rejected with an AbortError.
28-
*/
29-
signal?: AbortSignal;
30-
}
31-
}
5+
@example
6+
```
7+
import delay from 'delay';
328
33-
type Delay = {
34-
/**
35-
Create a promise which resolves after the specified `milliseconds`.
9+
const result = await delay(100, {value: '🦄'});
3610
37-
@param milliseconds - Milliseconds to delay the promise.
38-
@returns A promise which resolves after the specified `milliseconds`.
11+
// Executed after 100 milliseconds
12+
console.log(result);
13+
//=> '🦄'
14+
```
3915
*/
40-
(milliseconds: number, options?: delay.Options): delay.ClearablePromise<void>;
16+
value?: T;
4117

4218
/**
43-
Create a promise which resolves after the specified `milliseconds`.
19+
An `AbortSignal` to abort the delay.
4420
45-
@param milliseconds - Milliseconds to delay the promise.
46-
@returns A promise which resolves after the specified `milliseconds`.
47-
*/
48-
<T>(
49-
milliseconds: number,
50-
options?: delay.Options & {
51-
/**
52-
Value to resolve in the returned promise.
53-
*/
54-
value: T;
55-
}
56-
): delay.ClearablePromise<T>;
21+
The returned promise will be rejected with an `AbortError` if the signal is aborted.
5722
58-
/**
59-
Create a promise which resolves after a random amount of milliseconds between `minimum` and `maximum` has passed.
23+
@example
24+
```
25+
import delay from 'delay';
6026
61-
Useful for tests and web scraping since they can have unpredictable performance. For example, if you have a test that asserts a method should not take longer than a certain amount of time, and then run it on a CI, it could take longer. So with `.range()`, you could give it a threshold instead.
27+
const abortController = new AbortController();
6228
63-
@param minimum - Minimum amount of milliseconds to delay the promise.
64-
@param maximum - Maximum amount of milliseconds to delay the promise.
65-
@returns A promise which resolves after a random amount of milliseconds between `maximum` and `maximum` has passed.
66-
*/
67-
range<T>(
68-
minimum: number,
69-
maximum: number,
70-
options?: delay.Options & {
71-
/**
72-
Value to resolve in the returned promise.
73-
*/
74-
value: T;
75-
}
76-
): delay.ClearablePromise<T>;
77-
78-
// TODO: Allow providing value type after https://github.com/Microsoft/TypeScript/issues/5413 is resolved.
79-
/**
80-
Create a promise which rejects after the specified `milliseconds`.
29+
setTimeout(() => {
30+
abortController.abort();
31+
}, 500);
8132
82-
@param milliseconds - Milliseconds to delay the promise.
83-
@returns A promise which rejects after the specified `milliseconds`.
33+
try {
34+
await delay(1000, {signal: abortController.signal});
35+
} catch (error) {
36+
// 500 milliseconds later
37+
console.log(error.name)
38+
//=> 'AbortError'
39+
}
40+
```
8441
*/
85-
reject(
86-
milliseconds: number,
87-
options?: delay.Options & {
88-
/**
89-
Value to reject in the returned promise.
90-
*/
91-
value?: unknown;
92-
}
93-
): delay.ClearablePromise<never>;
42+
signal?: AbortSignal;
9443
};
9544

96-
declare const delay: Delay & {
97-
// The types are intentionally loose to make it work with both Node.js and browser versions of these methods.
98-
createWithTimers(timers: {
99-
clearTimeout: (timeoutId: any) => void;
100-
setTimeout: (callback: (...args: any[]) => void, milliseconds: number, ...args: any[]) => unknown;
101-
}): Delay;
45+
/**
46+
Create a promise which resolves after the specified `milliseconds`.
10247
103-
// TODO: Remove this for the next major release.
104-
default: typeof delay;
105-
};
48+
@param milliseconds - Milliseconds to delay the promise.
49+
@returns A promise which resolves after the specified `milliseconds`.
50+
51+
@example
52+
```
53+
import delay from 'delay';
54+
55+
bar();
56+
57+
await delay(100);
58+
59+
// Executed 100 milliseconds later
60+
baz();
61+
```
62+
*/
63+
export default function delay<T>(
64+
milliseconds: number,
65+
options?: Options<T>
66+
): Promise<T>;
67+
68+
/**
69+
Create a promise which resolves after a random amount of milliseconds between `minimum` and `maximum` has passed.
70+
71+
Useful for tests and web scraping since they can have unpredictable performance. For example, if you have a test that asserts a method should not take longer than a certain amount of time, and then run it on a CI, it could take longer. So with this method, you could give it a threshold instead.
72+
73+
@param minimum - Minimum amount of milliseconds to delay the promise.
74+
@param maximum - Maximum amount of milliseconds to delay the promise.
75+
@returns A promise which resolves after a random amount of milliseconds between `maximum` and `maximum` has passed.
76+
*/
77+
export function rangeDelay<T>(
78+
minimum: number,
79+
maximum: number,
80+
options?: Options<T>
81+
): Promise<T>;
82+
83+
/**
84+
Clears the delay and settles the promise.
85+
86+
If you pass in a promise that is already cleared or a promise coming from somewhere else, it does nothing.
87+
88+
@example
89+
```
90+
import delay, {clearDelay} from 'delay';
91+
92+
const delayedPromise = delay(1000, {value: 'Done'});
93+
94+
setTimeout(() => {
95+
clearDelay(delayedPromise);
96+
}, 500);
97+
98+
// 500 milliseconds later
99+
console.log(await delayedPromise);
100+
//=> 'Done'
101+
```
102+
*/
103+
export function clearDelay(delayPromise: Promise<unknown>): void;
104+
105+
// The types are intentionally loose to make it work with both Node.js and browser versions of these methods.
106+
/**
107+
Creates a new `delay` instance using the provided functions for clearing and setting timeouts. Useful if you're about to stub timers globally, but you still want to use `delay` to manage your tests.
108+
109+
@example
110+
```
111+
import {createDelay} from 'delay';
112+
113+
const customDelay = createDelay({clearTimeout, setTimeout});
114+
115+
const result = await customDelay(100, {value: '🦄'});
106116
107-
export = delay;
117+
// Executed after 100 milliseconds
118+
console.log(result);
119+
//=> '🦄'
120+
```
121+
*/
122+
export function createDelay(timers: {
123+
clearTimeout: (timeoutId: any) => void;
124+
setTimeout: (callback: (...args: any[]) => void, milliseconds: number, ...args: any[]) => unknown;
125+
}): typeof delay;

index.js

Lines changed: 47 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
'use strict';
2-
31
// From https://github.com/sindresorhus/random-int/blob/c37741b56f76b9160b0b63dae4e9c64875128146/index.js#L13-L15
42
const randomInteger = (minimum, maximum) => Math.floor((Math.random() * (maximum - minimum + 1)) + minimum);
53

@@ -9,64 +7,64 @@ const createAbortError = () => {
97
return error;
108
};
119

12-
const createDelay = ({clearTimeout: defaultClear, setTimeout: set, willResolve}) => (ms, {value, signal} = {}) => {
13-
if (signal && signal.aborted) {
14-
return Promise.reject(createAbortError());
15-
}
10+
const clearMethods = new WeakMap();
1611

17-
let timeoutId;
18-
let settle;
19-
let rejectFn;
20-
const clear = defaultClear || clearTimeout;
12+
export function createDelay({clearTimeout: defaultClear, setTimeout: defaultSet} = {}) {
13+
// We cannot use `async` here as we need the promise identity.
14+
return (milliseconds, {value, signal} = {}) => {
15+
// TODO: Use `signal?.throwIfAborted()` when targeting Node.js 18.
16+
if (signal?.aborted) {
17+
return Promise.reject(createAbortError());
18+
}
2119

22-
const signalListener = () => {
23-
clear(timeoutId);
24-
rejectFn(createAbortError());
25-
};
20+
let timeoutId;
21+
let settle;
22+
let rejectFunction;
23+
const clear = defaultClear ?? clearTimeout;
2624

27-
const cleanup = () => {
28-
if (signal) {
29-
signal.removeEventListener('abort', signalListener);
30-
}
31-
};
25+
const signalListener = () => {
26+
clear(timeoutId);
27+
rejectFunction(createAbortError());
28+
};
3229

33-
const delayPromise = new Promise((resolve, reject) => {
34-
settle = () => {
35-
cleanup();
36-
if (willResolve) {
37-
resolve(value);
38-
} else {
39-
reject(value);
30+
const cleanup = () => {
31+
if (signal) {
32+
signal.removeEventListener('abort', signalListener);
4033
}
4134
};
4235

43-
rejectFn = reject;
44-
timeoutId = (set || setTimeout)(settle, ms);
45-
});
36+
const delayPromise = new Promise((resolve, reject) => {
37+
settle = () => {
38+
cleanup();
39+
resolve(value);
40+
};
41+
42+
rejectFunction = reject;
43+
timeoutId = (defaultSet ?? setTimeout)(settle, milliseconds);
44+
});
4645

47-
if (signal) {
48-
signal.addEventListener('abort', signalListener, {once: true});
49-
}
46+
if (signal) {
47+
signal.addEventListener('abort', signalListener, {once: true});
48+
}
5049

51-
delayPromise.clear = () => {
52-
clear(timeoutId);
53-
timeoutId = null;
54-
settle();
50+
clearMethods.set(delayPromise, () => {
51+
clear(timeoutId);
52+
timeoutId = null;
53+
settle();
54+
});
55+
56+
return delayPromise;
5557
};
58+
}
5659

57-
return delayPromise;
58-
};
60+
const delay = createDelay();
5961

60-
const createWithTimers = clearAndSet => {
61-
const delay = createDelay({...clearAndSet, willResolve: true});
62-
delay.reject = createDelay({...clearAndSet, willResolve: false});
63-
delay.range = (minimum, maximum, options) => delay(randomInteger(minimum, maximum), options);
64-
return delay;
65-
};
62+
export default delay;
6663

67-
const delay = createWithTimers();
68-
delay.createWithTimers = createWithTimers;
64+
export async function rangeDelay(minimum, maximum, options = {}) {
65+
return delay(randomInteger(minimum, maximum), options);
66+
}
6967

70-
module.exports = delay;
71-
// TODO: Remove this for the next major release
72-
module.exports.default = delay;
68+
export function clearDelay(promise) {
69+
clearMethods.get(promise)?.();
70+
}

index.test-d.ts

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,25 @@
1-
/// <reference lib="dom"/>
21
import {expectType} from 'tsd';
3-
import delay = require('.');
4-
import {ClearablePromise} from '.';
2+
import delay, {rangeDelay, createDelay} from './index.js';
53

6-
expectType<ClearablePromise<void>>(delay(200));
4+
expectType<Promise<void>>(delay(200));
75

8-
expectType<ClearablePromise<string>>(delay(200, {value: '🦄'}));
9-
expectType<ClearablePromise<number>>(delay(200, {value: 0}));
10-
expectType<ClearablePromise<void>>(
11-
delay(200, {signal: new AbortController().signal})
6+
expectType<Promise<string>>(delay(200, {value: '🦄'}));
7+
expectType<Promise<number>>(delay(200, {value: 0}));
8+
expectType<Promise<void>>(
9+
delay(200, {signal: new AbortController().signal}),
1210
);
1311

14-
expectType<ClearablePromise<number>>(delay.range(50, 200, {value: 0}));
12+
expectType<Promise<number>>(rangeDelay(50, 200, {value: 0}));
1513

16-
expectType<ClearablePromise<never>>(delay.reject(200, {value: '🦄'}));
17-
expectType<ClearablePromise<never>>(delay.reject(200, {value: 0}));
14+
const customDelay = createDelay({clearTimeout, setTimeout});
15+
expectType<Promise<void>>(customDelay(200));
1816

19-
const customDelay = delay.createWithTimers({clearTimeout, setTimeout});
20-
expectType<ClearablePromise<void>>(customDelay(200));
17+
expectType<Promise<string>>(customDelay(200, {value: '🦄'}));
18+
expectType<Promise<number>>(customDelay(200, {value: 0}));
2119

22-
expectType<ClearablePromise<string>>(customDelay(200, {value: '🦄'}));
23-
expectType<ClearablePromise<number>>(customDelay(200, {value: 0}));
24-
25-
expectType<ClearablePromise<never>>(customDelay.reject(200, {value: '🦄'}));
26-
expectType<ClearablePromise<never>>(customDelay.reject(200, {value: 0}));
27-
28-
const unrefDelay = delay.createWithTimers({
20+
const unrefDelay = createDelay({
2921
clearTimeout,
30-
setTimeout(...args) {
31-
return setTimeout(...args).unref()
22+
setTimeout(...arguments_) {
23+
return setTimeout(...arguments_).unref();
3224
},
3325
});

0 commit comments

Comments
 (0)