Require Node.js 8, add TypeScript definition (#10)

This commit is contained in:
Dimitri Benin 2019-03-12 08:17:11 +00:00 committed by Sindre Sorhus
parent db65c4b511
commit 9a429bc248
7 changed files with 143 additions and 64 deletions

3
.gitattributes vendored
View File

@ -1,2 +1 @@
* text=auto * text=auto eol=lf
*.js text eol=lf

View File

@ -1,5 +1,4 @@
language: node_js language: node_js
node_js: node_js:
- '10'
- '8' - '8'
- '6'
- '4'

47
index.d.ts vendored Normal file
View File

@ -0,0 +1,47 @@
/**
* Timeout a promise after a specified amount of time.
*
* If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out.
*
* @param input - Promise to decorate.
* @param ms - Milliseconds before timing out.
* @param message - Specify a custom error message or error. If you do a custom error, it's recommended to sub-class `pTimeout.TimeoutError`. Default: `'Promise timed out after 50 milliseconds'`.
* @returns A decorated `input` that times out after `ms` time.
*/
export default function pTimeout<ValueType>(
input: PromiseLike<ValueType>,
ms: number,
message?: string | Error
): Promise<ValueType>;
/**
* Timeout a promise after a specified amount of time.
*
* If you pass in a cancelable promise, specifically a promise with a `.cancel()` method, that method will be called when the `pTimeout` promise times out.
*
* @param input - Promise to decorate.
* @param ms - Milliseconds before timing out.
* @param fallback - Do something other than rejecting with an error on timeout. You could for example retry.
* @returns A decorated `input` that times out after `ms` time.
*
* @example
*
* import delay from 'delay';
* import pTimeout from 'p-timeout';
*
* const delayedPromise = () => delay(200);
*
* pTimeout(delayedPromise(), 50, () => {
* return pTimeout(delayedPromise(), 300);
* });
*/
export default function pTimeout<ValueType, ReturnType>(
input: PromiseLike<ValueType>,
ms: number,
fallback: () => ReturnType | Promise<ReturnType>
): Promise<ValueType | ReturnType>;
export class TimeoutError extends Error {
readonly name: 'TimeoutError';
constructor(message?: string);
}

View File

@ -1,4 +1,5 @@
'use strict'; 'use strict';
const pFinally = require('p-finally'); const pFinally = require('p-finally');
class TimeoutError extends Error { class TimeoutError extends Error {
@ -8,7 +9,7 @@ class TimeoutError extends Error {
} }
} }
module.exports = (promise, ms, fallback) => new Promise((resolve, reject) => { const pTimeout = (promise, ms, fallback) => new Promise((resolve, reject) => {
if (typeof ms !== 'number' || ms < 0) { if (typeof ms !== 'number' || ms < 0) {
throw new TypeError('Expected `ms` to be a positive number'); throw new TypeError('Expected `ms` to be a positive number');
} }
@ -17,23 +18,25 @@ module.exports = (promise, ms, fallback) => new Promise((resolve, reject) => {
if (typeof fallback === 'function') { if (typeof fallback === 'function') {
try { try {
resolve(fallback()); resolve(fallback());
} catch (err) { } catch (error) {
reject(err); reject(error);
} }
return; return;
} }
const message = typeof fallback === 'string' ? fallback : `Promise timed out after ${ms} milliseconds`; const message = typeof fallback === 'string' ? fallback : `Promise timed out after ${ms} milliseconds`;
const err = fallback instanceof Error ? fallback : new TimeoutError(message); const timeoutError = fallback instanceof Error ? fallback : new TimeoutError(message);
if (typeof promise.cancel === 'function') { if (typeof promise.cancel === 'function') {
promise.cancel(); promise.cancel();
} }
reject(err); reject(timeoutError);
}, ms); }, ms);
pFinally( pFinally(
// eslint-disable-next-line promise/prefer-await-to-then
promise.then(resolve, reject), promise.then(resolve, reject),
() => { () => {
clearTimeout(timer); clearTimeout(timer);
@ -41,4 +44,7 @@ module.exports = (promise, ms, fallback) => new Promise((resolve, reject) => {
); );
}); });
module.exports = pTimeout;
module.exports.default = pTimeout;
module.exports.TimeoutError = TimeoutError; module.exports.TimeoutError = TimeoutError;

25
index.test-d.ts Normal file
View File

@ -0,0 +1,25 @@
import {expectType} from 'tsd-check';
import pTimeout, {TimeoutError} from '.';
const delayedPromise: () => Promise<string> = () =>
new Promise(resolve => setTimeout(() => resolve('foo'), 200));
pTimeout(delayedPromise(), 50).then(() => 'foo');
pTimeout(delayedPromise(), 50, () => {
return pTimeout(delayedPromise(), 300);
});
pTimeout(delayedPromise(), 50).then(value => expectType<string>(value));
pTimeout(delayedPromise(), 50, 'error').then(value =>
expectType<string>(value)
);
pTimeout(delayedPromise(), 50, new Error('error')).then(value =>
expectType<string>(value)
);
pTimeout(delayedPromise(), 50, async () => 10).then(value => {
expectType<string | number>(value);
});
pTimeout(delayedPromise(), 50, () => 10).then(value => {
expectType<string | number>(value);
});
expectType<typeof TimeoutError>(TimeoutError);

View File

@ -10,13 +10,14 @@
"url": "sindresorhus.com" "url": "sindresorhus.com"
}, },
"engines": { "engines": {
"node": ">=4" "node": ">=8"
}, },
"scripts": { "scripts": {
"test": "xo && ava" "test": "xo && ava && tsd-check"
}, },
"files": [ "files": [
"index.js" "index.js",
"index.d.ts"
], ],
"keywords": [ "keywords": [
"promise", "promise",
@ -35,9 +36,10 @@
"p-finally": "^1.0.0" "p-finally": "^1.0.0"
}, },
"devDependencies": { "devDependencies": {
"ava": "*", "ava": "^1.3.1",
"delay": "^2.0.0", "delay": "^4.1.0",
"p-cancelable": "^0.3.0", "p-cancelable": "^1.1.0",
"xo": "*" "tsd-check": "^0.3.0",
"xo": "^0.24.0"
} }
} }

29
test.js
View File

@ -1,49 +1,50 @@
import test from 'ava'; import test from 'ava';
import delay from 'delay'; import delay from 'delay';
import PCancelable from 'p-cancelable'; import PCancelable from 'p-cancelable';
import m from '.'; import pTimeout from '.';
const fixture = Symbol('fixture'); const fixture = Symbol('fixture');
const fixtureErr = new Error('fixture'); const fixtureErr = new Error('fixture');
test('resolves before timeout', async t => { test('resolves before timeout', async t => {
t.is(await m(delay(50).then(() => fixture), 200), fixture); t.is(await pTimeout(delay(50).then(() => fixture), 200), fixture);
}); });
test('throws when ms is not number', async t => { test('throws when ms is not number', async t => {
await t.throws(m(delay(50), '200'), TypeError); await t.throwsAsync(pTimeout(delay(50), '200'), TypeError);
}); });
test('throws when ms is negative number', async t => { test('throws when ms is negative number', async t => {
await t.throws(m(delay(50), -1), TypeError); await t.throwsAsync(pTimeout(delay(50), -1), TypeError);
}); });
test('rejects after timeout', async t => { test('rejects after timeout', async t => {
await t.throws(m(delay(200), 50), m.TimeoutError); await t.throwsAsync(pTimeout(delay(200), 50), pTimeout.TimeoutError);
}); });
test('rejects before timeout if specified promise rejects', async t => { test('rejects before timeout if specified promise rejects', async t => {
await t.throws(m(delay(50).then(() => Promise.reject(fixtureErr)), 200), fixtureErr.message); await t.throwsAsync(pTimeout(delay(50).then(() => Promise.reject(fixtureErr)), 200), fixtureErr.message);
}); });
test('fallback argument', async t => { test('fallback argument', async t => {
await t.throws(m(delay(200), 50, 'rainbow'), 'rainbow'); await t.throwsAsync(pTimeout(delay(200), 50, 'rainbow'), 'rainbow');
await t.throws(m(delay(200), 50, new RangeError('cake')), RangeError); await t.throwsAsync(pTimeout(delay(200), 50, new RangeError('cake')), RangeError);
await t.throws(m(delay(200), 50, () => Promise.reject(fixtureErr)), fixtureErr.message); await t.throwsAsync(pTimeout(delay(200), 50, () => Promise.reject(fixtureErr)), fixtureErr.message);
await t.throws(m(delay(200), 50, () => { await t.throwsAsync(pTimeout(delay(200), 50, () => {
throw new RangeError('cake'); throw new RangeError('cake');
}), RangeError); }), RangeError);
}); });
test('calls `.cancel()` on promise when it exists', async t => { test('calls `.cancel()` on promise when it exists', async t => {
const p = new PCancelable(onCancel => { const promise = new PCancelable(async (resolve, reject, onCancel) => {
onCancel(() => { onCancel(() => {
t.pass(); t.pass();
}); });
return delay(200); await delay(200);
resolve();
}); });
await t.throws(m(p, 50), m.TimeoutError); await t.throwsAsync(pTimeout(promise, 50), pTimeout.TimeoutError);
t.true(p.canceled); t.true(promise.isCanceled);
}); });