diff --git a/src/lib/handlers/restart.ts b/src/lib/handlers/restart.ts index daed5ec..3c0742f 100644 --- a/src/lib/handlers/restart.ts +++ b/src/lib/handlers/restart.ts @@ -2,21 +2,21 @@ import type { Handler } from './types'; type Handle = ReturnType; -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; diff --git a/src/lib/tests/components/restart.svelte b/src/lib/tests/components/restart.svelte new file mode 100644 index 0000000..f31a4e0 --- /dev/null +++ b/src/lib/tests/components/restart.svelte @@ -0,0 +1,66 @@ + + + + + + + + + + + + + diff --git a/src/lib/tests/task.test.ts b/src/lib/tests/task.test.ts index d9df755..087a10d 100644 --- a/src/lib/tests/task.test.ts +++ b/src/lib/tests/task.test.ts @@ -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)); @@ -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 () => { @@ -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); + }); + }); +}); diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 54a3501..a84986a 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -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));