Skip to content

Commit

Permalink
Add delay.createWithTimers() (#42)
Browse files Browse the repository at this point in the history
Fixes #39
  • Loading branch information
novemberborn authored and sindresorhus committed Oct 10, 2018
1 parent 34e5009 commit e589469
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 7 deletions.
6 changes: 5 additions & 1 deletion index.d.ts
Expand Up @@ -15,7 +15,7 @@ export interface Options {
signal?: AbortSignal
}

declare const delay: {
type Delay = {
/**
* Create a promise which resolves after the specified `milliseconds`.
*
Expand Down Expand Up @@ -46,6 +46,10 @@ declare const delay: {
/** Value to reject in the returned promise. */
value?: any
}): ClearablePromise<never>;
}

declare const delay: Delay & {
createWithTimers(timers: {clearTimeout: typeof clearTimeout, setTimeout: typeof setTimeout}): Delay
};

export default delay;
17 changes: 11 additions & 6 deletions index.js
Expand Up @@ -6,7 +6,7 @@ const createAbortError = () => {
return error;
};

const createDelay = willResolve => (ms, {value, signal} = {}) => {
const createDelay = ({clearTimeout: clear = clearTimeout, setTimeout: set = setTimeout, willResolve}) => (ms, {value, signal} = {}) => {
if (signal && signal.aborted) {
return Promise.reject(createAbortError());
}
Expand All @@ -16,7 +16,7 @@ const createDelay = willResolve => (ms, {value, signal} = {}) => {
let rejectFn;

const signalListener = () => {
clearTimeout(timeoutId);
clear(timeoutId);
rejectFn(createAbortError());
};

Expand All @@ -36,15 +36,15 @@ const createDelay = willResolve => (ms, {value, signal} = {}) => {
}
};
rejectFn = reject;
timeoutId = setTimeout(settle, ms);
timeoutId = set(settle, ms);
});

if (signal) {
signal.addEventListener('abort', signalListener, {once: true});
}

delayPromise.clear = () => {
clearTimeout(timeoutId);
clear(timeoutId);
timeoutId = null;
cleanup();
settle();
Expand All @@ -53,7 +53,12 @@ const createDelay = willResolve => (ms, {value, signal} = {}) => {
return delayPromise;
};

const delay = createDelay(true);
delay.reject = createDelay(false);
const delay = createDelay({willResolve: true});
delay.reject = createDelay({willResolve: false});
delay.createWithTimers = ({clearTimeout, setTimeout}) => {
const delay = createDelay({clearTimeout, setTimeout, willResolve: true});
delay.reject = createDelay({clearTimeout, setTimeout, willResolve: false});
return delay;
};
module.exports = delay;
module.exports.default = delay;
9 changes: 9 additions & 0 deletions index.test-d.ts
Expand Up @@ -8,3 +8,12 @@ expectType<number>(await delay(200, {value: 0}));

expectType<never>(await delay.reject(200, {value: '🦄'}));
expectType<never>(await delay.reject(200, {value: 0}));

const customDelay = delay.createWithTimers({clearTimeout, setTimeout})
expectType<void>(await customDelay(200));

expectType<string>(await customDelay(200, {value: '🦄'}));
expectType<number>(await customDelay(200, {value: 0}));

expectType<never>(await customDelay.reject(200, {value: '🦄'}));
expectType<never>(await customDelay.reject(200, {value: 0}));
19 changes: 19 additions & 0 deletions readme.md
Expand Up @@ -62,6 +62,9 @@ The returned promise will be rejected with an AbortError if the signal is aborte

Clears the delay and settles the promise.

### delay.createWithTimers({clearTimeout, setTimeout})

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.

## Advanced usage

Expand Down Expand Up @@ -137,6 +140,22 @@ const delay = require('delay');
})();
```

Create a new instance that is unaffected by libraries such as [lolex](https://github.com/sinonjs/lolex/):

```js
const delay = require('delay');

const customDelay = delay.createWithTimers({clearTimeout, setTimeout});

(async() => {
const result = await customDelay(100, {value: '🦄'});

// Executed after 100 milliseconds
console.log(result);
//=> '🦄'
})();
```


## Related

Expand Down
39 changes: 39 additions & 0 deletions test.js
Expand Up @@ -124,3 +124,42 @@ test('rejects with AbortError if AbortSignal is already aborted', async t => {
);
t.true(end() < 30);
});

test('can create a new instance with fixed timeout methods', async t => {
const cleared = [];
const callbacks = [];
const custom = m.createWithTimers({
clearTimeout(handle) {
cleared.push(handle);
},

setTimeout(callback, ms) {
const handle = Symbol('handle');
callbacks.push({callback, handle, ms});
return handle;
}
});

const first = custom(50, {value: 'first'});
t.is(callbacks.length, 1);
t.is(callbacks[0].ms, 50);
callbacks[0].callback();
t.is(await first, 'first');

const second = custom.reject(40, {value: 'second'});
t.is(callbacks.length, 2);
t.is(callbacks[1].ms, 40);
callbacks[1].callback();
try {
await second;
} catch (error) {
t.is(error, 'second');
}

const third = custom(60);
t.is(callbacks.length, 3);
t.is(callbacks[2].ms, 60);
third.clear();
t.is(cleared.length, 1);
t.is(cleared[0], callbacks[2].handle);
});

0 comments on commit e589469

Please sign in to comment.