mirror of
https://github.com/immich-app/immich.git
synced 2026-04-08 10:12:01 -04:00
fix(web): handle unhandled promise rejection in CancellableTask (#27553)
When a concurrent caller awaits `this.complete` inside `execute()` and `cancel()` is called, the promise rejects with `undefined` outside of any try/catch, causing "Uncaught (in promise) undefined" console spam during rapid timeline scrolling. - Wrap the `await this.complete` path in try/catch, returning 'CANCELED' - Guard the `finally` block to only null `cancelToken` if it still belongs to this call, preventing a race condition with `cancel()` to `init()` Change-Id: I65764dd664eb408433fc6e5fc2be4df56a6a6964
This commit is contained in:
parent
7f784952eb
commit
de9ec95db1
@ -161,6 +161,30 @@ describe('CancellableTask', () => {
|
||||
expect(task.executed).toBe(true);
|
||||
});
|
||||
|
||||
it('should return CANCELED when concurrent caller is waiting and task is canceled', async () => {
|
||||
const task = new CancellableTask();
|
||||
let resolveTask: () => void;
|
||||
const taskPromise = new Promise<void>((resolve) => {
|
||||
resolveTask = resolve;
|
||||
});
|
||||
const taskFn = async (signal: AbortSignal) => {
|
||||
await taskPromise;
|
||||
if (signal.aborted) {
|
||||
throw new DOMException('Aborted', 'AbortError');
|
||||
}
|
||||
};
|
||||
|
||||
const promise1 = task.execute(taskFn, true);
|
||||
const promise2 = task.execute(taskFn, true);
|
||||
|
||||
task.cancel();
|
||||
resolveTask!();
|
||||
|
||||
const [result1, result2] = await Promise.all([promise1, promise2]);
|
||||
expect(result1).toBe('CANCELED');
|
||||
expect(result2).toBe('CANCELED');
|
||||
});
|
||||
|
||||
it('should not cancel if task is already executed', async () => {
|
||||
const task = new CancellableTask();
|
||||
const taskFn = vi.fn(async () => {});
|
||||
|
||||
@ -64,8 +64,12 @@ export class CancellableTask {
|
||||
if (this.cancellable && !cancellable) {
|
||||
this.cancellable = cancellable;
|
||||
}
|
||||
await this.complete;
|
||||
return 'WAITED';
|
||||
try {
|
||||
await this.complete;
|
||||
return 'WAITED';
|
||||
} catch {
|
||||
return 'CANCELED';
|
||||
}
|
||||
}
|
||||
this.cancellable = cancellable;
|
||||
const cancelToken = (this.cancelToken = new AbortController());
|
||||
@ -86,7 +90,9 @@ export class CancellableTask {
|
||||
this.#transitionToErrored(error);
|
||||
return 'ERRORED';
|
||||
} finally {
|
||||
this.cancelToken = null;
|
||||
if (this.cancelToken === cancelToken) {
|
||||
this.cancelToken = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user