Compare commits

...

3 Commits

Author SHA1 Message Date
Jake Archibald
1c1e4c7c01 Better types 2021-02-05 14:36:36 +00:00
Jake Archibald
73ac08c0cd Don't need async here 2021-02-05 12:23:17 +00:00
Jake Archibald
7ff637a1ff Trying out abortableFunc 2021-02-05 12:11:33 +00:00
2 changed files with 49 additions and 30 deletions

View File

@@ -380,19 +380,46 @@ export function assertSignal(signal: AbortSignal) {
* Take a signal and promise, and returns a promise that rejects with an AbortError if the abort is * Take a signal and promise, and returns a promise that rejects with an AbortError if the abort is
* signalled, otherwise resolves with the promise. * signalled, otherwise resolves with the promise.
*/ */
export async function abortable<T>( export function abortable<T>(
signal: AbortSignal, signal: AbortSignal,
promise: Promise<T>, promise: Promise<T>,
): Promise<T> { ): Promise<T> {
assertSignal(signal); return abortableFunc(signal, () => promise);
return Promise.race([ }
promise,
type SetAbortArg = (() => void) | undefined;
type AbortableCallback<T> = (
setAbort: (abortCallback: SetAbortArg) => void,
) => Promise<T>;
/**
* A helper to create abortable things.
*
* @param signal Signal to abort the task
* @param callback The task
*/
export async function abortableFunc<T>(
signal: AbortSignal | undefined,
callback: AbortableCallback<T>,
): Promise<T> {
if (signal) assertSignal(signal);
let onAbort: (() => void) | undefined;
let listener: () => void;
const setOnAbort = (abortCallback: SetAbortArg) => {
onAbort = abortCallback;
};
const promise = callback(setOnAbort);
return Promise.race<T>([
new Promise<T>((_, reject) => { new Promise<T>((_, reject) => {
signal.addEventListener('abort', () => listener = () => {
reject(new DOMException('AbortError', 'AbortError')), onAbort?.();
); reject(new DOMException('AbortError', 'AbortError'));
};
signal?.addEventListener('abort', listener);
}), }),
]); promise,
]).finally(() => signal?.removeEventListener('abort', listener));
} }
/** /**

View File

@@ -2,7 +2,7 @@ import { wrap } from 'comlink';
import { BridgeMethods, methodNames } from './meta'; import { BridgeMethods, methodNames } from './meta';
import workerURL from 'omt:../../../features-worker'; import workerURL from 'omt:../../../features-worker';
import type { ProcessorWorkerApi } from '../../../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. */ /** How long the worker should be idle before terminating. */
const workerTimeout = 10_000; const workerTimeout = 10_000;
@@ -40,29 +40,21 @@ for (const methodName of methodNames) {
this._queue = this._queue this._queue = this._queue
// Ignore any errors in the queue // Ignore any errors in the queue
.catch(() => {}) .catch(() => {})
.then(async () => { .then(() =>
if (signal.aborted) throw new DOMException('AbortError', 'AbortError'); abortableFunc(signal, async (setOnAbort) => {
clearTimeout(this._workerTimeout);
if (!this._worker) this._startWorker();
clearTimeout(this._workerTimeout); setOnAbort(() => this._terminateWorker());
if (!this._worker) this._startWorker();
const onAbort = () => this._terminateWorker(); return this._workerApi![methodName](...args).finally(() => {
signal.addEventListener('abort', onAbort); // Start a timer to clear up the worker.
this._workerTimeout = setTimeout(() => {
return abortable( this._terminateWorker();
signal, }, workerTimeout);
// @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._queue; return this._queue;
} as any; } as any;