diff --git a/src/client/lazy-app/util/index.ts b/src/client/lazy-app/util/index.ts index e26274dc..bb06974b 100644 --- a/src/client/lazy-app/util/index.ts +++ b/src/client/lazy-app/util/index.ts @@ -384,15 +384,41 @@ export async function abortable( signal: AbortSignal, promise: Promise, ): Promise { - assertSignal(signal); - return Promise.race([ - promise, + return abortableFunc(signal, () => promise); +} + +type AbortableCallback = ( + setAbort: (abortCallback: () => void) => void, +) => Promise; + +/** + * A helper to create abortable things. + * + * @param signal Signal to abort the task + * @param callback The task + */ +export async function abortableFunc( + signal: AbortSignal | undefined, + callback: AbortableCallback, +): Promise { + if (signal) assertSignal(signal); + let onAbort: () => void; + let listener: () => void; + const setOnAbort = (abortCallback: () => void) => { + onAbort = abortCallback; + }; + const promise = callback(setOnAbort); + + return Promise.race([ new Promise((_, reject) => { - signal.addEventListener('abort', () => - reject(new DOMException('AbortError', 'AbortError')), - ); + listener = () => { + onAbort?.(); + reject(new DOMException('AbortError', 'AbortError')); + }; + signal?.addEventListener('abort', listener); }), - ]); + promise, + ]).finally(() => signal?.removeEventListener('abort', listener)); } /** diff --git a/src/client/lazy-app/worker-bridge/index.ts b/src/client/lazy-app/worker-bridge/index.ts index 1babcef8..4b3cb752 100644 --- a/src/client/lazy-app/worker-bridge/index.ts +++ b/src/client/lazy-app/worker-bridge/index.ts @@ -2,7 +2,7 @@ import { wrap } from 'comlink'; import { BridgeMethods, methodNames } from './meta'; import workerURL from 'omt:../../../features-worker'; import type { ProcessorWorkerApi } from '../../../features-worker'; -import { abortable } from '../util'; +import { abortableFunc } from '../util'; /** How long the worker should be idle before terminating. */ const workerTimeout = 10_000; @@ -40,29 +40,21 @@ for (const methodName of methodNames) { this._queue = this._queue // Ignore any errors in the queue .catch(() => {}) - .then(async () => { - if (signal.aborted) throw new DOMException('AbortError', 'AbortError'); + .then(() => + abortableFunc(signal, async (setOnAbort) => { + clearTimeout(this._workerTimeout); + if (!this._worker) this._startWorker(); - clearTimeout(this._workerTimeout); - if (!this._worker) this._startWorker(); + setOnAbort(() => this._terminateWorker()); - const onAbort = () => this._terminateWorker(); - signal.addEventListener('abort', onAbort); - - return abortable( - signal, - // @ts-ignore - TypeScript can't figure this out - this._workerApi![methodName](...args), - ).finally(() => { - // No longer care about aborting - this task is complete. - signal.removeEventListener('abort', onAbort); - - // Start a timer to clear up the worker. - this._workerTimeout = setTimeout(() => { - this._terminateWorker(); - }, workerTimeout); - }); - }); + return this._workerApi![methodName](...args).finally(() => { + // Start a timer to clear up the worker. + this._workerTimeout = setTimeout(() => { + this._terminateWorker(); + }, workerTimeout); + }); + }), + ); return this._queue; } as any;