From 085f437e495fb5a31d20e94c4cf7aacec4847644 Mon Sep 17 00:00:00 2001 From: Pedro Augusto de Paula Barbosa Date: Tue, 1 Dec 2020 12:40:53 -0300 Subject: [PATCH] Add `customTimers` option (#17) Co-authored-by: Sindre Sorhus --- index.d.ts | 39 +++++++++++++++++++++++++++++++++++++-- index.js | 11 ++++++++--- index.test-d.ts | 16 +++++++++++++++- package.json | 2 +- readme.md | 38 ++++++++++++++++++++++++++++++++++++-- test.js | 17 +++++++++++++++++ 6 files changed, 114 insertions(+), 9 deletions(-) diff --git a/index.d.ts b/index.d.ts index 57d909f..c0f624f 100644 --- a/index.d.ts +++ b/index.d.ts @@ -5,6 +5,39 @@ declare class TimeoutErrorClass extends Error { declare namespace pTimeout { type TimeoutError = TimeoutErrorClass; + + type Options = { + /** + Custom implementations for the `setTimeout` and `clearTimeout` functions. + + Useful for testing purposes, in particular to work around [`sinon.useFakeTimers()`](https://sinonjs.org/releases/latest/fake-timers/). + + @example + ``` + import pTimeout = require('p-timeout'); + import sinon = require('sinon'); + + (async () => { + const originalSetTimeout = setTimeout; + const originalClearTimeout = clearTimeout; + + sinon.useFakeTimers(); + + // Use `pTimeout` without being affected by `sinon.useFakeTimers()`: + await pTimeout(doSomething(), 2000, undefined, { + customTimers: { + setTimeout: originalSetTimeout, + clearTimeout: originalClearTimeout + } + }); + })(); + ``` + */ + readonly customTimers?: { + setTimeout: typeof global.setTimeout; + clearTimeout: typeof global.clearTimeout; + }; + } } declare const pTimeout: { @@ -32,7 +65,8 @@ declare const pTimeout: { ( input: PromiseLike, milliseconds: number, - message?: string | Error + message?: string | Error, + options?: pTimeout.Options ): Promise; /** @@ -60,7 +94,8 @@ declare const pTimeout: { ( input: PromiseLike, milliseconds: number, - fallback: () => ReturnType | Promise + fallback: () => ReturnType | Promise, + options?: pTimeout.Options ): Promise; TimeoutError: typeof TimeoutErrorClass; diff --git a/index.js b/index.js index 34d4a4c..e26438d 100644 --- a/index.js +++ b/index.js @@ -9,7 +9,7 @@ class TimeoutError extends Error { } } -const pTimeout = (promise, milliseconds, fallback) => new Promise((resolve, reject) => { +const pTimeout = (promise, milliseconds, fallback, options) => new Promise((resolve, reject) => { if (typeof milliseconds !== 'number' || milliseconds < 0) { throw new TypeError('Expected `milliseconds` to be a positive number'); } @@ -19,7 +19,12 @@ const pTimeout = (promise, milliseconds, fallback) => new Promise((resolve, reje return; } - const timer = setTimeout(() => { + options = { + customTimers: {setTimeout, clearTimeout}, + ...options + }; + + const timer = options.customTimers.setTimeout(() => { if (typeof fallback === 'function') { try { resolve(fallback()); @@ -45,7 +50,7 @@ const pTimeout = (promise, milliseconds, fallback) => new Promise((resolve, reje // eslint-disable-next-line promise/prefer-await-to-then promise.then(resolve, reject), () => { - clearTimeout(timer); + options.customTimers.clearTimeout(timer); } ); }); diff --git a/index.test-d.ts b/index.test-d.ts index f81bea3..a77942e 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -1,4 +1,4 @@ -import {expectType} from 'tsd'; +import {expectType, expectError} from 'tsd'; import pTimeout = require('.'); import {TimeoutError} from '.'; @@ -23,5 +23,19 @@ pTimeout(delayedPromise(), 50, () => 10).then(value => { expectType(value); }); +const customTimers = {setTimeout, clearTimeout}; +pTimeout(delayedPromise(), 50, undefined, {customTimers}); +pTimeout(delayedPromise(), 50, 'foo', {customTimers}); +pTimeout(delayedPromise(), 50, new Error('error'), {customTimers}); +pTimeout(delayedPromise(), 50, () => 10, {}); + +expectError(pTimeout(delayedPromise(), 50, () => 10, {customTimers: {setTimeout}})); +expectError(pTimeout(delayedPromise(), 50, () => 10, { + customTimers: { + setTimeout: () => 42, // Invalid `setTimeout` implementation + clearTimeout + } +})); + const timeoutError = new TimeoutError(); expectType(timeoutError); diff --git a/package.json b/package.json index 0c8b4c3..a506bfd 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "ava": "^1.4.1", "delay": "^4.1.0", "p-cancelable": "^2.0.0", - "tsd": "^0.7.2", + "tsd": "^0.13.1", "xo": "^0.24.0" } } diff --git a/readme.md b/readme.md index 3bf13f3..593d067 100644 --- a/readme.md +++ b/readme.md @@ -22,8 +22,8 @@ pTimeout(delayedPromise, 50).then(() => 'foo'); ## API -### pTimeout(input, milliseconds, message?) -### pTimeout(input, milliseconds, fallback?) +### pTimeout(input, milliseconds, message?, options?) +### pTimeout(input, milliseconds, fallback?, options?) Returns a decorated `input` that times out after `milliseconds` time. @@ -71,6 +71,40 @@ pTimeout(delayedPromise(), 50, () => { }); ``` +#### options + +Type: `object` + +##### customTimers + +Type: `object` with function properties `setTimeout` and `clearTimeout` + +Custom implementations for the `setTimeout` and `clearTimeout` functions. + +Useful for testing purposes, in particular to work around [`sinon.useFakeTimers()`](https://sinonjs.org/releases/latest/fake-timers/). + +Example: + +```js +const pTimeout = require('p-timeout'); +const sinon = require('sinon'); + +(async () => { + const originalSetTimeout = setTimeout; + const originalClearTimeout = clearTimeout; + + sinon.useFakeTimers(); + + // Use `pTimeout` without being affected by `sinon.useFakeTimers()`: + await pTimeout(doSomething(), 2000, undefined, { + customTimers: { + setTimeout: originalSetTimeout, + clearTimeout: originalClearTimeout + } + }); +})(); +``` + ### pTimeout.TimeoutError Exposed for instance checking and sub-classing. diff --git a/test.js b/test.js index 4d6ba10..aa1ac30 100644 --- a/test.js +++ b/test.js @@ -55,3 +55,20 @@ test('calls `.cancel()` on promise when it exists', async t => { await t.throwsAsync(pTimeout(promise, 50), pTimeout.TimeoutError); t.true(promise.isCanceled); }); + +test('accepts `customTimers` option', async t => { + t.plan(2); + + await pTimeout(delay(50), 123, undefined, { + customTimers: { + setTimeout(fn, milliseconds) { + t.is(milliseconds, 123); + return setTimeout(fn, milliseconds); + }, + clearTimeout(timeoutId) { + t.pass(); + return clearTimeout(timeoutId); + } + } + }); +});