Skip to content

Commit

Permalink
👩‍💻 dx: First draft for defer utility.
Browse files Browse the repository at this point in the history
  • Loading branch information
make-github-pseudonymous-again committed Jul 25, 2024
1 parent 3d14f2e commit 18763d1
Show file tree
Hide file tree
Showing 2 changed files with 314 additions and 0 deletions.
235 changes: 235 additions & 0 deletions imports/lib/async/defer.tests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,235 @@
import {assert} from 'chai';

import {client} from '../../_test/fixtures';

import {cancelAll, defer, flushAll} from './defer';
import sleep from './sleep';

client(__filename, () => {
it('should queue to macrotask queue', async () => {
let i = 0;

Check warning on line 11 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L6-L11

Added lines #L6 - L11 were not covered by tests
defer(() => ++i);

assert.strictEqual(i, 0);

Check warning on line 14 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L13-L14

Added lines #L13 - L14 were not covered by tests

await Promise.resolve().then(() => {
assert.strictEqual(i, 0);
});

await sleep(0);

Check warning on line 21 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L16-L21

Added lines #L16 - L21 were not covered by tests
assert.strictEqual(i, 1);
});

it('should allow cancellation', async () => {

Check warning on line 25 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L23-L25

Added lines #L23 - L25 were not covered by tests
let i = 0;

const deferred = defer(() => ++i);

assert.strictEqual(i, 0);

await Promise.resolve().then(() => {
assert.strictEqual(i, 0);

Check warning on line 33 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L27-L33

Added lines #L27 - L33 were not covered by tests
});

deferred.cancel();

Check warning on line 36 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L35-L36

Added lines #L35 - L36 were not covered by tests

await sleep(0);

assert.strictEqual(i, 0);
});

it('should allow flushing before microtask queue', async () => {
let i = 0;

Check warning on line 44 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L38-L44

Added lines #L38 - L44 were not covered by tests

const deferred = defer(() => ++i);

Check warning on line 47 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L46-L47

Added lines #L46 - L47 were not covered by tests
assert.strictEqual(i, 0);

deferred.flush();

await Promise.resolve().then(() => {
assert.strictEqual(i, 1);

Check warning on line 53 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L49-L53

Added lines #L49 - L53 were not covered by tests
});

await sleep(0);

Check warning on line 56 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L55-L56

Added lines #L55 - L56 were not covered by tests

assert.strictEqual(i, 1);
});

it('should flush after main loop', async () => {
let i = 0;

Check warning on line 62 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L58-L62

Added lines #L58 - L62 were not covered by tests

const deferred = defer(() => ++i);

deferred.flush();

Check warning on line 66 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L64-L66

Added lines #L64 - L66 were not covered by tests

assert.strictEqual(i, 0);

await Promise.resolve().then(() => {
assert.strictEqual(i, 1);
});

await sleep(0);

Check warning on line 74 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L68-L74

Added lines #L68 - L74 were not covered by tests

assert.strictEqual(i, 1);
});

Check warning on line 78 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L76-L78

Added lines #L76 - L78 were not covered by tests
it('should catch errors', async () => {
let i = 0;

defer(() => {
++i;
throw new Error('test');
});

await sleep(0);

Check warning on line 87 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L80-L87

Added lines #L80 - L87 were not covered by tests

assert.strictEqual(i, 1);
});

Check warning on line 90 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L89-L90

Added lines #L89 - L90 were not covered by tests

it('should catch errors when flushing', async () => {
let i = 0;

const deferred = defer(() => {
++i;
throw new Error('test');
});

Check warning on line 99 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L92-L99

Added lines #L92 - L99 were not covered by tests
deferred.flush();

await sleep(0);

Check warning on line 102 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L101-L102

Added lines #L101 - L102 were not covered by tests

assert.strictEqual(i, 1);
});

it('should allow cancellation of all deferred computations', async () => {

Check warning on line 107 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L104-L107

Added lines #L104 - L107 were not covered by tests
let i = 0;

defer(() => ++i);

Check warning on line 110 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L109-L110

Added lines #L109 - L110 were not covered by tests
defer(() => ++i);

assert.strictEqual(i, 0);

Check warning on line 114 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L112-L114

Added lines #L112 - L114 were not covered by tests
await Promise.resolve().then(() => {
assert.strictEqual(i, 0);
});

Check warning on line 117 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L116-L117

Added lines #L116 - L117 were not covered by tests

cancelAll();

await sleep(0);

Check warning on line 122 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L119-L122

Added lines #L119 - L122 were not covered by tests
assert.strictEqual(i, 0);
});

Check warning on line 125 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L124-L125

Added lines #L124 - L125 were not covered by tests
it('should allow flushing all deferred computations before microtask queue', async () => {
let i = 0;

defer(() => ++i);

Check warning on line 129 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L127-L129

Added lines #L127 - L129 were not covered by tests
defer(() => ++i);

assert.strictEqual(i, 0);

flushAll();

await Promise.resolve().then(() => {

Check warning on line 136 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L131-L136

Added lines #L131 - L136 were not covered by tests
assert.strictEqual(i, 2);
});

await sleep(0);

Check warning on line 141 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L138-L141

Added lines #L138 - L141 were not covered by tests
assert.strictEqual(i, 2);
});

it('should flush all after main loop', async () => {

Check warning on line 145 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L143-L145

Added lines #L143 - L145 were not covered by tests
let i = 0;

defer(() => ++i);

Check warning on line 148 in imports/lib/async/defer.tests.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.tests.ts#L147-L148

Added lines #L147 - L148 were not covered by tests
defer(() => ++i);

flushAll();

assert.strictEqual(i, 0);

await Promise.resolve().then(() => {
assert.strictEqual(i, 2);
});

await sleep(0);

assert.strictEqual(i, 2);
});

it('should execute in order', async () => {
let x = 'a';

defer(() => {
x = 'b';
});
defer(() => {
x = 'c';
});

assert.strictEqual(x, 'a');

await Promise.resolve().then(() => {
assert.strictEqual(x, 'a');
});

await sleep(0);

assert.strictEqual(x, 'c');
});

it('should respect timeout', async () => {
let x = 'a';

defer(() => {
x = 'b';
}, 1);
defer(() => {
x = 'c';
});

assert.strictEqual(x, 'a');

await Promise.resolve().then(() => {
assert.strictEqual(x, 'a');
});

await sleep(0);

assert.strictEqual(x, 'c');

await sleep(0);

assert.strictEqual(x, 'c');

await sleep(1);

assert.strictEqual(x, 'b');
});

it('should allow passing arguments', async () => {
let z = 0;
defer(
(x, y) => {
z = x + y;
},
0,
1,
2,
);

assert.strictEqual(z, 0);

await Promise.resolve().then(() => {
assert.strictEqual(z, 0);
});

await sleep(0);

assert.strictEqual(z, 3);
});
});
79 changes: 79 additions & 0 deletions imports/lib/async/defer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import type Timeout from '../types/Timeout';

import createPromise from './createPromise';

type Resolve = (value?: any) => void;
type Reject = (reason?: any) => void;

type Callback<A extends any[]> = (...args: A) => void;

const _pending = new Set<Deferred>();

Check warning on line 10 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L8-L10

Added lines #L8 - L10 were not covered by tests

export class Deferred {
#timeout: Timeout;
#resolve: Resolve;
#reject: Reject;

constructor(timeout: Timeout, resolve: Resolve, reject: Reject) {

Check warning on line 17 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L14-L17

Added lines #L14 - L17 were not covered by tests
this.#timeout = timeout;
this.#resolve = resolve;
this.#reject = reject;
}

cancel() {
if (!_pending.has(this)) return;

Check warning on line 24 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L21-L24

Added lines #L21 - L24 were not covered by tests
_pending.delete(this);
clearTimeout(this.#timeout);
this.#reject();
}

Check warning on line 29 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L28-L29

Added lines #L28 - L29 were not covered by tests
flush() {
if (!_pending.has(this)) return;
_pending.delete(this);

Check warning on line 32 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L31-L32

Added lines #L31 - L32 were not covered by tests
clearTimeout(this.#timeout);
this.#resolve();
}
}

Check warning on line 37 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L37

Added line #L37 was not covered by tests
export const defer = <A extends any[]>(
callback: Callback<A>,
timeout?: number,
...args: A

Check warning on line 41 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L39-L41

Added lines #L39 - L41 were not covered by tests
): Deferred => {
const {promise, resolve, reject} = createPromise();
promise
.then(

Check warning on line 45 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L44-L45

Added lines #L44 - L45 were not covered by tests
() => {
_pending.delete(deferred);
callback(...args);
},

Check warning on line 49 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L48-L49

Added lines #L48 - L49 were not covered by tests

() => {
// NOTE This handles cancellation.
},

Check warning on line 53 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L52-L53

Added lines #L52 - L53 were not covered by tests
)
.catch((error: unknown) => {
console.error({error});
});

Check warning on line 57 in imports/lib/async/defer.ts

View check run for this annotation

Codecov / codecov/patch

imports/lib/async/defer.ts#L56-L57

Added lines #L56 - L57 were not covered by tests
const deferred = new Deferred(setTimeout(resolve, timeout), resolve, reject);
_pending.add(deferred);
return deferred;
};

const _cancelAll = (pending: Iterable<Deferred>) => {
for (const deferred of pending) deferred.cancel();
};

export const cancelAll = () => {
_cancelAll(_pending);
_pending.clear();
};

const _flushAll = (pending: Iterable<Deferred>) => {
for (const deferred of pending) deferred.flush();
};

export const flushAll = () => {
_flushAll(_pending);
_pending.clear();
};

0 comments on commit 18763d1

Please sign in to comment.