Skip to content

Commit

Permalink
Test and fix delay, timeout
Browse files Browse the repository at this point in the history
  • Loading branch information
BobKerns committed Mar 12, 2023
1 parent b1fecd1 commit 74b43a3
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 16 deletions.
72 changes: 69 additions & 3 deletions src/__tests__/test-future.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* Copyright 2023 by Bob Kerns. Licensed under MIT license.
*/

import { CancelContext, Future, withThis } from "..";
import { CancelContext, Future, Throw, TimeoutException, withThis } from "..";
import { FutureState } from "../future-state";
import { MethodSpec, is, isState, isStatic, isInstance, hasTag, p_never } from "./tools";

Expand Down Expand Up @@ -33,6 +33,12 @@ const methods: Array<MethodSpec<Future<any>, typeof Future>> = [
];

describe("Basic", () => {
// Check for broken p_never
test("p_never", async () => {
const p = new Promise((resolve, reject) =>
setTimeout(resolve, 100));
await Promise.race([p, p_never.then(() => Throw("never happened"))]);
});
describe("API completeness", () => {
test("Future is a function", () =>
expect(Future)
Expand Down Expand Up @@ -82,6 +88,8 @@ describe("Basic", () => {
}
})()).toBeInstanceOf(Error)
});
});
describe("Future functional", () => {
test('Future.runnable runnable', async () => {
let c: CancelContext<any>;
const f = new Future(withThis((ctx: CancelContext<any>) => (c = ctx))).start();
Expand All @@ -102,9 +110,10 @@ describe("Basic", () => {
expect(f.isCancelled).toBe(false);
});

// Test starting a simple task.
describe('Start' , () => {
test ("Never", () => {
const fNever = new Future(p_never);
test ("Never but started", () => {
const fNever = new Future(() => p_never);
expect(fNever.start().state).toBe('RUNNING');
});
test ("Immediate", () => {
Expand All @@ -128,5 +137,62 @@ describe("Basic", () => {
.resolves.toBe('REJECTED');
});
});

describe("delay", () => {
test("delay", async () => {
const f = Future.delay(100)(() => 1);
expect(f.state).toBe('PENDING');
const startTime = Date.now();
expect(f.start().state).toBe('DELAY');
await f;
expect(Date.now() - startTime).toBeGreaterThan(99.9)
expect(f.state).toBe('FULFILLED');
});
});

describe("timeout", () => {
test("timeout", async () => {
const createTime = Date.now();
const f = Future.timeout(100)(() => p_never);
await Future.delay(100)(() => 1);
expect(f.state).toBe('PENDING');
const startTime = Date.now();
expect(f.start().state).toBe('RUNNING');
try {
await f;
throw new Error("Did not time out");
} catch (e) {
if (!(e instanceof TimeoutException)) throw e;
expect(e).toBeInstanceOf(TimeoutException);
expect(Date.now() - startTime).toBeGreaterThan(99.9);
expect(Date.now() - startTime).toBeLessThan(150);
expect(Date.now() - createTime).toBeGreaterThan(199.9);
expect(e.endTime - e.startTime).toBeLessThan(150);
expect(e.endTime - startTime).toBeLessThan(150);
expect(f.state).toBe('TIMEOUT');
}
});
});
describe("timeoutFromNow", () => {
test("timeoutFromNow", async () => {
const createTime = Date.now();
const f = Future.timeoutFromNow(100)(() => p_never);
await Future.delay(50)(() => 1);
expect(f.state).toBe('PENDING');
const startTime = Date.now();
expect(f.start().state).toBe('RUNNING');
try {
await f;
throw new Error("Did not time out");
} catch (e) {
if (!(e instanceof TimeoutException)) throw e;
expect(e).toBeInstanceOf(TimeoutException);
expect(Date.now() - createTime).toBeGreaterThan(99.9);
expect(e.endTime - createTime).toBeGreaterThanOrEqual(100);
expect(e.endTime - startTime).toBeLessThan(100);
expect(f.state).toBe('TIMEOUT');
}
});
});
});
});
2 changes: 1 addition & 1 deletion src/__tests__/tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import {State, Future} from '..';

export const p_never = () => new Promise(() => {})
export const p_never = new Promise(() => {});

export type MethodTags = 'static' | 'constructor' | 'instance' | 'field';

Expand Down
31 changes: 19 additions & 12 deletions src/future.ts
Original file line number Diff line number Diff line change
Expand Up @@ -424,9 +424,14 @@ export class Future<T> {
*/
static delay(delay: Millis): <T>(a: Task<T>) => Future<T> {
return <T>(task: Task<T>) => {
const p = new Promise((resolve, reject) => setTimeout(resolve, delay));
const f: SimpleTask<T> = simple(task);
const future: Future<T> = new Future<T>(() => p.then(() => future.#s.call(f)));
const future: Future<T> = new Future<T>(async () => {
const p = new Promise((resolve, reject) => setTimeout(resolve, delay));
const f: SimpleTask<T> = simple(task);
future.#s.state = State.DELAY;
await p;
future.#s.state = State.RUNNING;
return f.call(future.#s.context);
});
return future;
};
}
Expand All @@ -444,15 +449,16 @@ export class Future<T> {
static timeoutFromNow(timeout: Millis, msg = "Timeout") {
const msg_dflt = msg;
return <T>(task: Task<T>, msg: string = msg_dflt) => {
// Start the timer now
const start = Date.now();
const future: Future<T> = new Future<T>(async (): Promise<T> => {
let future: Future<T>;
const p = new Promise<TimeoutException<T>>((resolve, reject) =>
setTimeout(() => reject(new TimeoutException(future, msg)), timeout)
).catch(e => (future.#s.onTimeout?.(e), Throw(e)));
// Start the timer now
future = new Future<T>(async (): Promise<T> => {
const c = Promise.resolve<T>(future.#s.call(simple(task))).then(
(v) => ((future.#s.onTimeout = null), v)
);
const p = new Promise<TimeoutException<T>>((resolve, reject) =>
setTimeout(() => reject(new TimeoutException(future, msg, Date.now())), timeout)
).catch(e => (future.#s.onTimeout?.(e), Throw(e)));
return await Promise.race([c, p]) as T;
});
return future;
Expand All @@ -477,8 +483,11 @@ export class Future<T> {
// Start the timer when the Future executes.
const start = Date.now();
const p = new Promise<TimeoutException<T>>((resolve, reject) =>
setTimeout(() => resolve(new TimeoutException<T>(future, tmsg, start)), timeout)
).then((e) => (future.#s.onTimeout?.(e), Throw(e)));
setTimeout(() => reject(new TimeoutException<T>(future, tmsg, start)), timeout)
).catch((e) => {
future.#s.onTimeout?.(e);
throw e;
});
const f = simple(task);
const c: Promise<T> = Promise.resolve(future.#s.call(f)).then(
(v) => ((future.#s.onTimeout = null), v)
Expand Down Expand Up @@ -600,6 +609,4 @@ export class Future<T> {
static any<T>(thenables: Iterable<PromiseLike<T>>): Future<T> {
return new Future<T>(() => Promise.any(thenables));
}


}

0 comments on commit 74b43a3

Please sign in to comment.