Skip to content

Commit

Permalink
Merge pull request #45 from mainmatter/add-max-to-restartable
Browse files Browse the repository at this point in the history
feat: add max to restartable
  • Loading branch information
paoloricciuti authored Apr 5, 2024
2 parents adcaec5 + 81f6d58 commit fe1945f
Show file tree
Hide file tree
Showing 4 changed files with 161 additions and 10 deletions.
12 changes: 6 additions & 6 deletions src/lib/handlers/restart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,21 @@ import type { Handler } from './types';

type Handle = ReturnType<Handler>;

const handler = (() => {
let running_controller: null | AbortController;
const handler = (({ max = 1 }: { max?: number } = { max: 1 }) => {
const running_controllers: AbortController[] = [];

const handle: Handle = async (fn: () => void, utils) => {
if (running_controller) {
running_controller.abort();
if (running_controllers.length >= max) {
running_controllers.shift()?.abort();
}
running_controller = utils.abort_controller;
running_controllers.push(utils.abort_controller);
try {
fn();
await utils.promise;
} catch {
/** empty */
}
running_controller = null;
running_controllers.pop();
};
return handle;
}) satisfies Handler;
Expand Down
66 changes: 66 additions & 0 deletions src/lib/tests/components/restart.svelte
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
<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.restart(fn, { max });
const options_task = task(fn, { kind: 'restart', 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
>
79 changes: 79 additions & 0 deletions src/lib/tests/task.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { describe, expect, it, vi } from 'vitest';
import Default from './components/default.svelte';
import Enqueue from './components/enqueue.svelte';
import Drop from './components/drop.svelte';
import Restart from './components/restart.svelte';

function wait(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
Expand All @@ -20,6 +21,7 @@ describe.each([
{ component: Default, name: 'default' },
{ component: Enqueue, name: 'enqueue' },
{ component: Drop, name: 'drop' },
{ component: Restart, name: 'restart' },
])('task - basic functionality $name', ({ component }) => {
all_options((selector) => {
it('calls the function you pass in', async () => {
Expand Down Expand Up @@ -324,3 +326,80 @@ describe("task - specific functionality 'drop'", () => {
});
});
});

describe("task - specific functionality 'restart'", () => {
all_options((selector) => {
it('completes only `max` time if performed when other instances are already running', async () => {
let finished = 0;
const abort_signals: AbortSignal[] = [];
const fn = vi.fn(async function* (_: number, { signal }: SvelteConcurrencyUtils) {
abort_signals.push(signal);
await wait(50);
yield;
finished++;
});
const { getByTestId } = render(Restart, {
fn,
max: 1,
});
const perform = getByTestId(`perform-${selector}`);
perform.click();
perform.click();
perform.click();
await vi.waitFor(() => {
expect(fn).toHaveBeenCalledTimes(3);
});
await vi.waitFor(() => {
expect(finished).toBe(1);
});
expect(abort_signals.map((signal) => signal.aborted)).toStrictEqual([true, true, false]);
perform.click();
await vi.waitFor(() => {
expect(finished).toBe(2);
});
expect(abort_signals.at(-1)?.aborted).toBe(false);
});

it('completes only `max` time if performed when other instances are already running (max: 3)', async () => {
let finished = 0;
const abort_signals: AbortSignal[] = [];
const fn = vi.fn(async function* (_: number, { signal }: SvelteConcurrencyUtils) {
abort_signals.push(signal);
await wait(50);
yield;
finished++;
});
const { getByTestId } = render(Restart, {
fn,
max: 3,
});
const perform = getByTestId(`perform-${selector}`);
perform.click();
perform.click();
perform.click();
perform.click();
await vi.waitFor(() => {
expect(abort_signals[0]?.aborted).toBe(true);
});
perform.click();
await vi.waitFor(() => {
expect(fn).toHaveBeenCalledTimes(5);
});
await vi.waitFor(() => {
expect(finished).toBe(3);
});
expect(abort_signals.map((signal) => signal.aborted)).toStrictEqual([
true,
true,
false,
false,
false,
]);
perform.click();
await vi.waitFor(() => {
expect(finished).toBe(4);
});
expect(abort_signals.at(-1)?.aborted).toBe(false);
});
});
});
14 changes: 10 additions & 4 deletions src/routes/+page.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,16 @@
{ max: 5 },
);
const restart_log = task.restart(async (param: number) => {
await new Promise((r) => setTimeout(r, 2000));
return param;
});
const restart_log = task.restart(
async function* (param: number) {
console.log('started ', param);
await new Promise((r) => setTimeout(r, 2000));
yield;
console.log('finished ', param);
return param;
},
{ max: 3 },
);
const drop_log = task.drop(async (param: number) => {
await new Promise((r) => setTimeout(r, 2000));
Expand Down

0 comments on commit fe1945f

Please sign in to comment.