Skip to content

Commit

Permalink
WIP [ci skip]
Browse files Browse the repository at this point in the history
  • Loading branch information
CMCDragonkai committed Aug 12, 2022
1 parent 8d9cab1 commit 331c732
Show file tree
Hide file tree
Showing 2 changed files with 56 additions and 4 deletions.
40 changes: 36 additions & 4 deletions src/timer/TimeOut.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,39 @@
import * as timerErrors from './errors';

class TimeOut<T = void> {
class TimeOut<T = void> implements Promise<T> {
/**
* Deconstructed promise
*/
protected p: Promise<T>;

/**
* Resolve deconstructed promise
*/
protected resolveP: (value?: T) => void;

/**
* Reject deconstructed promise
*/
protected rejectP: (reason?: timerErrors.ErrorTimer<any>) => void;

/**
* Internal timeout reference
*/
protected timeout: ReturnType<typeof setTimeout>;

/**
* Whether the timer has timed out
* This is only `true` when the timer resolves
* If the timer rejects, this stays `false`
*/
protected _isTimedOut: boolean = false;

constructor(opts?: { delay?: number; handler?: () => T });
constructor({
handler,
delay = 0,
handler = () => {},
}: {
handler?: () => T;
delay?: number;
} = {}) {
this.p = new Promise<T>((resolve, reject) => {
this.resolveP = resolve.bind(this.p);
Expand All @@ -24,7 +42,7 @@ class TimeOut<T = void> {
this.timeout = setTimeout(() => {
this._isTimedOut = true;
if (handler != null) {
this.resolveP(handler() as unknown as T);
this.resolveP(handler());
} else {
this.resolveP();
}
Expand All @@ -35,13 +53,27 @@ class TimeOut<T = void> {
return this._isTimedOut;
}

get [Symbol.toStringTag](): string {
return this.constructor.name;
}

public then<TResult1 = T, TResult2 = never>(
onFulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>) | undefined | null,
onRejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>) | undefined | null
): Promise<TResult1 | TResult2> {
return this.p.then(onFulfilled, onRejected);
}

public catch<TResult = never>(
onRejected?: ((reason: any) => TResult | PromiseLike<TResult>) | undefined | null
): Promise<T | TResult> {
return this.p.catch(onRejected);
}

public finally(onFinally?: (() => void) | undefined | null): Promise<T> {
return this.p.finally(onFinally);
}

public cancel() {
clearTimeout(this.timeout);
this.rejectP(new timerErrors.ErrorTimeOutCancelled());
Expand Down
20 changes: 20 additions & 0 deletions tests/timer/TimeOut.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { TimeOut } from '@/timer';
import * as timerErrors from '@/timer/errors';

describe(TimeOut.name, () => {
test('timeout is thenable and awaitable', async () => {
Expand All @@ -23,4 +24,23 @@ describe(TimeOut.name, () => {
expect(await t2).toBe('123');
expect(t2.isTimedOut).toBe(true);
});
test('timeout cancellation', async () => {
const t1 = new TimeOut({ delay: 100 });
t1.cancel();
await expect(t1).rejects.toThrow(timerErrors.ErrorTimeOutCancelled);
const t2 = new TimeOut({ delay: 100 });
const results = await Promise.all([
(async () => {
try {
await t2;
} catch (e) {
return e;
}
})(),
(async () => {
t2.cancel();
})()
]);
expect(results[0]).toBeInstanceOf(timerErrors.ErrorTimeOutCancelled);
});
});

0 comments on commit 331c732

Please sign in to comment.