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
*.js text eol=lf
* text=auto eol=lf

View File

@ -1,5 +1,4 @@
language: node_js
node_js:
- '10'
- '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';
const pFinally = require('p-finally');
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) {
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') {
try {
resolve(fallback());
} catch (err) {
reject(err);
} catch (error) {
reject(error);
}
return;
}
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') {
promise.cancel();
}
reject(err);
reject(timeoutError);
}, ms);
pFinally(
// eslint-disable-next-line promise/prefer-await-to-then
promise.then(resolve, reject),
() => {
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;

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

29
test.js
View File

@ -1,49 +1,50 @@
import test from 'ava';
import delay from 'delay';
import PCancelable from 'p-cancelable';
import m from '.';
import pTimeout from '.';
const fixture = Symbol('fixture');
const fixtureErr = new Error('fixture');
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 => {
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 => {
await t.throws(m(delay(50), -1), TypeError);
await t.throwsAsync(pTimeout(delay(50), -1), TypeError);
});
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 => {
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 => {
await t.throws(m(delay(200), 50, 'rainbow'), 'rainbow');
await t.throws(m(delay(200), 50, new RangeError('cake')), RangeError);
await t.throws(m(delay(200), 50, () => Promise.reject(fixtureErr)), fixtureErr.message);
await t.throws(m(delay(200), 50, () => {
await t.throwsAsync(pTimeout(delay(200), 50, 'rainbow'), 'rainbow');
await t.throwsAsync(pTimeout(delay(200), 50, new RangeError('cake')), RangeError);
await t.throwsAsync(pTimeout(delay(200), 50, () => Promise.reject(fixtureErr)), fixtureErr.message);
await t.throwsAsync(pTimeout(delay(200), 50, () => {
throw new RangeError('cake');
}), RangeError);
});
test('calls `.cancel()` on promise when it exists', async t => {
const p = new PCancelable(onCancel => {
const promise = new PCancelable(async (resolve, reject, onCancel) => {
onCancel(() => {
t.pass();
});
return delay(200);
await delay(200);
resolve();
});
await t.throws(m(p, 50), m.TimeoutError);
t.true(p.canceled);
await t.throwsAsync(pTimeout(promise, 50), pTimeout.TimeoutError);
t.true(promise.isCanceled);
});