diff --git a/src/lib/handlers/keep_latest.ts b/src/lib/handlers/keep_latest.ts new file mode 100644 index 0000000..4be144e --- /dev/null +++ b/src/lib/handlers/keep_latest.ts @@ -0,0 +1,39 @@ +import type { Handler } from './types'; + +type Handle = ReturnType; +type Fn = Parameters[0]; +type Utils = Parameters[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; diff --git a/src/lib/task.ts b/src/lib/task.ts index 6e617f6..1d3122c 100644 --- a/src/lib/task.ts +++ b/src/lib/task.ts @@ -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; diff --git a/src/lib/tests/components/keep_latest.svelte b/src/lib/tests/components/keep_latest.svelte new file mode 100644 index 0000000..d39fb8d --- /dev/null +++ b/src/lib/tests/components/keep_latest.svelte @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + diff --git a/src/lib/tests/task.test.ts b/src/lib/tests/task.test.ts index ba9b372..a6a6b0f 100644 --- a/src/lib/tests/task.test.ts +++ b/src/lib/tests/task.test.ts @@ -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)); @@ -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) => { @@ -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 () => { diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4ef88ae..a10f91f 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -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[] = [];
@@ -100,6 +110,35 @@
+
+ latest_log +
{JSON.stringify($latest_log, null, '	')}
+ + + +
    + {#each numbers as number} +
  • + {number} +
  • + {/each} +
+
+