Skip to content

Commit

Permalink
Merge pull request #56 from mainmatter/dbeer/keep-latest
Browse files Browse the repository at this point in the history
Add keep_latest
  • Loading branch information
paoloricciuti authored May 17, 2024
2 parents 3305727 + 12a7d6b commit f52f17c
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 0 deletions.
39 changes: 39 additions & 0 deletions src/lib/handlers/keep_latest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import type { Handler } from './types';

type Handle = ReturnType<Handler>;
type Fn = Parameters<Handle>[0];
type Utils = Parameters<Handle>[1];

const handler = (({ max = 1 }: { max?: number } = { max: 1 }) => {
let running = 0;
let latest:
| {
fn: Fn;
utils: Utils;
}
| undefined = undefined;

const handle: Handle = async (fn: () => void, utils) => {
if (running >= max) {
latest?.utils.abort_controller.abort();
latest = { fn, utils };
return;
}
running++;
try {
fn();
await utils.promise;
} catch {
/** empty */
}
running--;

if (latest) {
handle(latest.fn, latest.utils);
latest = undefined;
}
};
return handle;
}) satisfies Handler;

export default handler;
2 changes: 2 additions & 0 deletions src/lib/task.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import { writable } from 'svelte/store';
import default_handler from './handlers/default';
import drop from './handlers/drop';
import enqueue from './handlers/enqueue';
import keep_latest from './handlers/keep_latest';
import restart from './handlers/restart';

const handlers = {
default: default_handler,
drop,
enqueue,
keepLatest: keep_latest,
restart,
} as const;

Expand Down
80 changes: 80 additions & 0 deletions src/lib/tests/components/keep_latest.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<script lang="ts">
import { task, type SvelteConcurrencyUtils } from '../../task';
export let fn: (
args: number,
utils: SvelteConcurrencyUtils,
) => Promise<unknown> | AsyncGenerator<unknown, unknown, unknown>;
export let return_value: (value: unknown) => void = () => {};
export let argument = 0;
export let max = 1;
const default_task = task.keepLatest(fn, { max });
const options_task = task(fn, { kind: 'keepLatest', max });
let latest_task_instance: ReturnType<typeof default_task.perform>;
let latest_options_task_instance: ReturnType<typeof options_task.perform>;
</script>

<button
data-testid="perform-default"
on:click={async () => {
latest_task_instance = default_task.perform(argument);
return_value(await latest_task_instance);
}}>perform</button
>

<button
data-testid="perform-options"
on:click={async () => {
latest_options_task_instance = options_task.perform(argument);
return_value(await latest_options_task_instance);
}}>perform options</button
>

<button
data-testid="cancel-default"
on:click={() => {
default_task.cancelAll();
}}>cancel</button
>

<button
data-testid="cancel-options"
on:click={() => {
options_task.cancelAll();
}}>cancel options</button
>

<button
data-testid="cancel-default-last"
on:click={() => {
if (latest_task_instance) {
latest_task_instance.cancel();
}
}}>cancel last instance</button
>

<button
data-testid="cancel-options-last"
on:click={() => {
if (latest_options_task_instance) {
latest_options_task_instance.cancel();
}
}}>cancel last options instance</button
>

<button
data-testid="perform-error"
on:click={async () => {
try {
await default_task.perform(argument);
} catch (e) {
return_value({
error: e,
store: default_task,
});
}
}}>perform</button
>
63 changes: 63 additions & 0 deletions src/lib/tests/task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import Link from './components/link/parent.svelte';
import WrongKind from './components/wrong-kind.svelte';
import type { Task, SvelteConcurrencyUtils } from '../index';
import { get } from 'svelte/store';
import KeepLatest from './components/keep_latest.svelte';

function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
Expand All @@ -24,6 +25,7 @@ describe.each([
{ component: Default, name: 'default' },
{ component: Enqueue, name: 'enqueue' },
{ component: Drop, name: 'drop' },
{ component: KeepLatest, name: 'keepLatest' },
{ component: Restart, name: 'restart' },
])('task - basic functionality $name', ({ component }) => {
all_options((selector) => {
Expand Down Expand Up @@ -361,6 +363,67 @@ describe("task - specific functionality 'drop'", () => {
});
});

describe("task - specific functionality 'keepLatest'", () => {
all_options((selector) => {
it('completes only `max` + 1 times if performed when other instances are already running', async () => {
let finished = 0;
const fn = vi.fn(async function* () {
await wait(50);
yield;
finished++;
});
const { getByTestId } = render(KeepLatest, {
fn,
});
const perform = getByTestId(`perform-${selector}`);
perform.click();
perform.click();
perform.click();
perform.click();
await vi.waitFor(() => {
expect(fn).toHaveBeenCalledTimes(2);
});
await vi.waitFor(() => {
expect(finished).toBe(2);
});
perform.click();
await vi.waitFor(() => {
expect(finished).toBe(3);
});
});

it('completes only `max` + 1 times if performed when other instances are already running (max: 3)', async () => {
let finished = 0;
const fn = vi.fn(async function* () {
await wait(50);
yield;
finished++;
});
const { getByTestId } = render(KeepLatest, {
fn,
max: 3,
});
const perform = getByTestId(`perform-${selector}`);
perform.click();
perform.click();
perform.click();
perform.click();
perform.click();
perform.click();
await vi.waitFor(() => {
expect(fn).toHaveBeenCalledTimes(4);
});
await vi.waitFor(() => {
expect(finished).toBe(4);
});
perform.click();
await vi.waitFor(() => {
expect(finished).toBe(4);
});
});
});
});

describe("task - specific functionality 'restart'", () => {
all_options((selector) => {
it('completes only `max` time if performed when other instances are already running', async () => {
Expand Down
39 changes: 39 additions & 0 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,19 @@
return param;
});
const latest_log = task(
async (param: number) => {
await new Promise((r) => setTimeout(r, 2000));
return param;
},
{ kind: 'keepLatest', max: 3 },
);
let hidden = false;
let x;
let numbers: number[] = [];
</script>

<fieldset>
Expand Down Expand Up @@ -100,6 +110,35 @@
</button>
</fieldset>

<fieldset>
<legend>latest_log</legend>
<pre>{JSON.stringify($latest_log, null, ' ')}</pre>

<button
on:click={() => {
const num = Math.random();
numbers = [...numbers, num];
latest_log.perform(num);
}}
>
Perform
</button>
<button
on:click={() => {
numbers = [];
}}
>
Clear numbers
</button>
<ul>
{#each numbers as number}
<li>
{number}
</li>
{/each}
</ul>
</fieldset>

<button
on:click={() => {
hidden = !hidden;
Expand Down

0 comments on commit f52f17c

Please sign in to comment.