Skip to content

Commit

Permalink
added timeout utility function (#9)
Browse files Browse the repository at this point in the history
  • Loading branch information
sha1n authored Jan 27, 2022
1 parent 90bf326 commit 82f4fe4
Show file tree
Hide file tree
Showing 4 changed files with 103 additions and 4 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ A collection of essential time related utilities.
- [About-Time](#about-time)
- [Install](#install)
- [Delay](#delay)
- [WithTimeout](#withtimeout)
- [Sleep](#sleep)
- [Stopwatch](#stopwatch)
- [Until / Eventually](#until--eventually)
Expand All @@ -35,6 +36,12 @@ npm i @sha1n/about-time
await delay(action, 10, TimeUnit.Milliseconds);
```

## WithTimeout
```ts
// Execute a function and guards it with a specified timeout
await withTimeout(action, 10, TimeUnit.Milliseconds);
```

## Sleep
```ts
// Pause execution for a specified amount of time
Expand Down
52 changes: 49 additions & 3 deletions lib/utilities.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { TimeUnit, toMilliseconds } from './timeunit';

class TimeoutError extends Error {
constructor(message?: string) {
super(message || 'Timeout');
}
}

/**
* Zzzz...
*
Expand All @@ -24,7 +30,10 @@ function sleep(time: number, units?: TimeUnit): Promise<void> {
function delay<T>(action: () => T | Promise<T>, time: number, units?: TimeUnit): Promise<T> {
const delayMs = toMilliseconds(time, units);
return new Promise((resolve, reject) => {
setTimeout(() => Promise.resolve(action()).then(resolve, reject), delayMs);
const timer = setTimeout(() => Promise.resolve(action()).then(resolve, reject), delayMs);
process.on('beforeExit', () => {
clearTimeout(timer);
});
});
}

Expand Down Expand Up @@ -59,7 +68,7 @@ async function until(
const handle = setInterval(() => {
if (Date.now() > deadline) {
clearInterval(handle);
reject(new Error('Timeout'));
reject(new TimeoutError());
}

try {
Expand All @@ -80,4 +89,41 @@ async function until(
*/
const eventually = until;

export { sleep, delay, stopwatch, until, eventually };
/**
* Executes an action with a specified timeout. If the action times out, rejects with TimeoutError.
*
* @param action an action to execute with timeout
* @param timeout the timeout to set for the action
* @param units the time units
* @returns the action result
*/
async function withTimeout<T>(action: () => T | Promise<T>, timeout: number, units?: TimeUnit): Promise<T> {
const promisedAction = new Promise<T>((resolve, reject) => {
try {
resolve(action());
} catch (e) {
reject(e);
}
});

const race = new Promise<T>((resolve, reject) => {
const timer = setTimeout(() => {
reject(new TimeoutError());
}, toMilliseconds(timeout, units));

return Promise.resolve(promisedAction).then(
r => {
clearTimeout(timer);
resolve(r);
},
err => {
clearTimeout(timer);
reject(err);
}
);
});

return race;
}

export { withTimeout, sleep, delay, stopwatch, until, eventually };
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sha1n/about-time",
"version": "0.0.5",
"version": "0.0.6",
"description": "A set of essential time related utilities",
"repository": "https://github.com/sha1n/about-time",
"author": "Shai Nagar",
Expand Down
46 changes: 46 additions & 0 deletions test/withTimeout.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'jest-extended';
import { TimeUnit } from '../lib/timeunit';
import { until, withTimeout } from '../lib/utilities';
import { anError, aString } from './randoms';

describe('withTimeout', () => {
const expectedError = anError();
const expectedValue = aString();

test('should reject when the action rejects', async () => {
const action = () => Promise.reject(expectedError);

await expect(withTimeout(action, 1, TimeUnit.Minute)).rejects.toThrow(expectedError);
});

test('should reject when the action throws', async () => {
const action = () => {
throw expectedError;
};

await expect(withTimeout(action, 1, TimeUnit.Minute)).rejects.toThrow(expectedError);
});

test('should resolve to the action resolved value when resolves on time', async () => {
const action = () => Promise.resolve(expectedValue);

await expect(withTimeout(action, 1, TimeUnit.Minute)).resolves.toEqual(expectedValue);
});

test('should resolve to the action returned value when returns on time', async () => {
const action = () => expectedValue;

await expect(withTimeout(action, 1, TimeUnit.Minute)).resolves.toEqual(expectedValue);
});

// eslint-disable-next-line prettier/prettier
test('should reject with timeout when the action doesn\'t resolve on time', async () => {
let done = false;
const longAction = async () => {
await until(() => done);
};

await expect(withTimeout(longAction, 1, TimeUnit.Millisecond)).rejects.toThrow(/Timeout/);
done = true;
});
});

0 comments on commit 82f4fe4

Please sign in to comment.