mirror of
https://github.com/immich-app/immich.git
synced 2026-05-21 07:06:31 -04:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 206a07d8db | |||
| 0a93963041 |
@@ -145,7 +145,7 @@ describe('TimelineManager', () => {
|
|||||||
it('cancels month loading', async () => {
|
it('cancels month loading', async () => {
|
||||||
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
|
const month = getTimelineMonthByDate(timelineManager, { year: 2024, month: 1 })!;
|
||||||
void timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
void timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||||
const abortSpy = vi.spyOn(month!.loader!.cancelToken!, 'abort');
|
const abortSpy = vi.spyOn(month!.loader!.abortController!, 'abort');
|
||||||
month?.cancel();
|
month?.cancel();
|
||||||
expect(abortSpy).toBeCalledTimes(1);
|
expect(abortSpy).toBeCalledTimes(1);
|
||||||
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
await timelineManager.loadTimelineMonth({ year: 2024, month: 1 });
|
||||||
@@ -638,12 +638,8 @@ describe('TimelineManager', () => {
|
|||||||
const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 });
|
const previousMonth = getTimelineMonthByDate(timelineManager, { year: 2024, month: 3 });
|
||||||
const a = month!.getFirstAsset();
|
const a = month!.getFirstAsset();
|
||||||
const b = previousMonth!.getFirstAsset();
|
const b = previousMonth!.getFirstAsset();
|
||||||
const loadTimelineMonthSpy = vi.spyOn(month!.loader!, 'execute');
|
|
||||||
const previousMonthSpy = vi.spyOn(previousMonth!.loader!, 'execute');
|
|
||||||
const previous = await timelineManager.getLaterAsset(a);
|
const previous = await timelineManager.getLaterAsset(a);
|
||||||
expect(previous).toEqual(b);
|
expect(previous).toEqual(b);
|
||||||
expect(loadTimelineMonthSpy).toBeCalledTimes(0);
|
|
||||||
expect(previousMonthSpy).toBeCalledTimes(0);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('skips removed assets', async () => {
|
it('skips removed assets', async () => {
|
||||||
|
|||||||
@@ -307,8 +307,8 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!this.initTask.executed) {
|
if (!this.initTask.succeeded) {
|
||||||
await (this.initTask.loading ? this.initTask.waitUntilCompletion() : this.#init(this.#options));
|
await (this.initTask.running ? this.initTask.waitUntilCompletion() : this.#init(this.#options));
|
||||||
}
|
}
|
||||||
|
|
||||||
const changedWidth = viewport.width !== this.viewportWidth;
|
const changedWidth = viewport.width !== this.viewportWidth;
|
||||||
@@ -351,14 +351,10 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (timelineMonth.loader?.executed) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const executionStatus = await timelineMonth.loader?.execute(async (signal: AbortSignal) => {
|
const executionStatus = await timelineMonth.loader?.execute(async (signal: AbortSignal) => {
|
||||||
await loadFromTimeBuckets(this, timelineMonth, this.#options, signal);
|
await loadFromTimeBuckets(this, timelineMonth, this.#options, signal);
|
||||||
}, cancelable);
|
}, cancelable);
|
||||||
if (executionStatus === 'LOADED') {
|
if (executionStatus === 'SUCCESS') {
|
||||||
updateGeometry(this, timelineMonth, { invalidateHeight: false });
|
updateGeometry(this, timelineMonth, { invalidateHeight: false });
|
||||||
this.updateViewportProximities();
|
this.updateViewportProximities();
|
||||||
}
|
}
|
||||||
@@ -372,7 +368,7 @@ export class TimelineManager extends VirtualScrollManager {
|
|||||||
|
|
||||||
async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) {
|
async findTimelineMonthForAsset(asset: AssetDescriptor | AssetResponseDto) {
|
||||||
if (!this.isInitialized) {
|
if (!this.isInitialized) {
|
||||||
await this.initTask.waitUntilExecution();
|
await this.initTask.waitUntilSucceeded();
|
||||||
}
|
}
|
||||||
|
|
||||||
const { id } = asset;
|
const { id } = asset;
|
||||||
|
|||||||
@@ -2,39 +2,39 @@ import { CancellableTask } from '$lib/utils/cancellable-task';
|
|||||||
|
|
||||||
describe('CancellableTask', () => {
|
describe('CancellableTask', () => {
|
||||||
describe('execute', () => {
|
describe('execute', () => {
|
||||||
it('should execute task successfully and return LOADED', async () => {
|
it('should execute task successfully and return SUCCESS', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async (_: AbortSignal) => {
|
const taskFunction = vi.fn(async (_: AbortSignal) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('LOADED');
|
expect(result).toBe('SUCCESS');
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
expect(task.loading).toBe(false);
|
expect(task.running).toBe(false);
|
||||||
expect(taskFn).toHaveBeenCalledTimes(1);
|
expect(taskFunction).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call loadedCallback when task completes successfully', async () => {
|
it('should call succeededCallback when task completes successfully', async () => {
|
||||||
const loadedCallback = vi.fn();
|
const succeededCallback = vi.fn();
|
||||||
const task = new CancellableTask(loadedCallback);
|
const task = new CancellableTask(succeededCallback);
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(loadedCallback).toHaveBeenCalledTimes(1);
|
expect(succeededCallback).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return DONE if task is already executed', async () => {
|
it('should return DONE if task is already executed', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('DONE');
|
expect(result).toBe('DONE');
|
||||||
expect(taskFn).toHaveBeenCalledTimes(1);
|
expect(taskFunction).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should wait if task is already running', async () => {
|
it('should wait if task is already running', async () => {
|
||||||
@@ -43,42 +43,42 @@ describe('CancellableTask', () => {
|
|||||||
const taskPromise = new Promise<void>((resolve) => {
|
const taskPromise = new Promise<void>((resolve) => {
|
||||||
resolveTask = resolve;
|
resolveTask = resolve;
|
||||||
});
|
});
|
||||||
const taskFn = vi.fn(async () => {
|
const taskFunction = vi.fn(async () => {
|
||||||
await taskPromise;
|
await taskPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise1 = task.execute(taskFn, true);
|
const promise1 = task.execute(taskFunction, true);
|
||||||
const promise2 = task.execute(taskFn, true);
|
const promise2 = task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(task.loading).toBe(true);
|
expect(task.running).toBe(true);
|
||||||
resolveTask!();
|
resolveTask!();
|
||||||
|
|
||||||
const [result1, result2] = await Promise.all([promise1, promise2]);
|
const [result1, result2] = await Promise.all([promise1, promise2]);
|
||||||
|
|
||||||
expect(result1).toBe('LOADED');
|
expect(result1).toBe('SUCCESS');
|
||||||
expect(result2).toBe('WAITED');
|
expect(result2).toBe('WAITED');
|
||||||
expect(taskFn).toHaveBeenCalledTimes(1);
|
expect(taskFunction).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should pass AbortSignal to task function', async () => {
|
it('should pass AbortSignal to task function', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let capturedSignal: AbortSignal | null = null;
|
let capturedSignal: AbortSignal | null = null;
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
capturedSignal = signal;
|
capturedSignal = signal;
|
||||||
};
|
};
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(capturedSignal).toBeInstanceOf(AbortSignal);
|
expect(capturedSignal).toBeInstanceOf(AbortSignal);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should set cancellable flag correctly', async () => {
|
it('should set cancellable flag correctly', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
expect(task.cancellable).toBe(true);
|
expect(task.cancellable).toBe(true);
|
||||||
const promise = task.execute(taskFn, false);
|
const promise = task.execute(taskFunction, false);
|
||||||
expect(task.cancellable).toBe(false);
|
expect(task.cancellable).toBe(false);
|
||||||
await promise;
|
await promise;
|
||||||
});
|
});
|
||||||
@@ -89,14 +89,14 @@ describe('CancellableTask', () => {
|
|||||||
const taskPromise = new Promise<void>((resolve) => {
|
const taskPromise = new Promise<void>((resolve) => {
|
||||||
resolveTask = resolve;
|
resolveTask = resolve;
|
||||||
});
|
});
|
||||||
const taskFn = vi.fn(async () => {
|
const taskFunction = vi.fn(async () => {
|
||||||
await taskPromise;
|
await taskPromise;
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise1 = task.execute(taskFn, false);
|
const promise1 = task.execute(taskFunction, false);
|
||||||
expect(task.cancellable).toBe(false);
|
expect(task.cancellable).toBe(false);
|
||||||
|
|
||||||
const promise2 = task.execute(taskFn, true);
|
const promise2 = task.execute(taskFunction, true);
|
||||||
expect(task.cancellable).toBe(false);
|
expect(task.cancellable).toBe(false);
|
||||||
|
|
||||||
resolveTask!();
|
resolveTask!();
|
||||||
@@ -108,7 +108,7 @@ describe('CancellableTask', () => {
|
|||||||
it('should cancel a running task', async () => {
|
it('should cancel a running task', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let taskStarted = false;
|
let taskStarted = false;
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
taskStarted = true;
|
taskStarted = true;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
@@ -116,9 +116,7 @@ describe('CancellableTask', () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
|
|
||||||
// Wait a bit to ensure task has started
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
expect(taskStarted).toBe(true);
|
expect(taskStarted).toBe(true);
|
||||||
|
|
||||||
@@ -126,20 +124,20 @@ describe('CancellableTask', () => {
|
|||||||
|
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
expect(result).toBe('CANCELED');
|
expect(result).toBe('CANCELED');
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call canceledCallback when task is canceled', async () => {
|
it('should call canceledCallback when task is canceled', async () => {
|
||||||
const canceledCallback = vi.fn();
|
const canceledCallback = vi.fn();
|
||||||
const task = new CancellableTask(undefined, canceledCallback);
|
const task = new CancellableTask(undefined, canceledCallback);
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
task.cancel();
|
task.cancel();
|
||||||
await promise;
|
await promise;
|
||||||
@@ -149,55 +147,79 @@ describe('CancellableTask', () => {
|
|||||||
|
|
||||||
it('should not cancel if task is not cancellable', async () => {
|
it('should not cancel if task is not cancellable', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {
|
const taskFunction = vi.fn(async () => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 50));
|
await new Promise((resolve) => setTimeout(resolve, 50));
|
||||||
});
|
});
|
||||||
|
|
||||||
const promise = task.execute(taskFn, false);
|
const promise = task.execute(taskFunction, false);
|
||||||
task.cancel();
|
task.cancel();
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
|
|
||||||
expect(result).toBe('LOADED');
|
expect(result).toBe('SUCCESS');
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).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 taskFunction = async (signal: AbortSignal) => {
|
||||||
|
await taskPromise;
|
||||||
|
if (signal.aborted) {
|
||||||
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const promise1 = task.execute(taskFunction, true);
|
||||||
|
const promise2 = task.execute(taskFunction, 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 () => {
|
it('should not cancel if task is already executed', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
|
|
||||||
task.cancel();
|
task.cancel();
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('reset', () => {
|
describe('reset', () => {
|
||||||
it('should reset task to initial state', async () => {
|
it('should reset task to initial state', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
|
|
||||||
await task.reset();
|
await task.reset();
|
||||||
|
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
expect(task.cancelToken).toBe(null);
|
expect(task.abortController).toBe(null);
|
||||||
expect(task.loading).toBe(false);
|
expect(task.running).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should cancel running task before resetting', async () => {
|
it('should cancel running task before resetting', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
|
||||||
const resetPromise = task.reset();
|
const resetPromise = task.reset();
|
||||||
@@ -205,30 +227,30 @@ describe('CancellableTask', () => {
|
|||||||
await promise;
|
await promise;
|
||||||
await resetPromise;
|
await resetPromise;
|
||||||
|
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
expect(task.loading).toBe(false);
|
expect(task.running).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow re-execution after reset', async () => {
|
it('should allow re-execution after reset', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
await task.reset();
|
await task.reset();
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('LOADED');
|
expect(result).toBe('SUCCESS');
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
expect(taskFn).toHaveBeenCalledTimes(2);
|
expect(taskFunction).toHaveBeenCalledTimes(2);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('waitUntilCompletion', () => {
|
describe('waitUntilCompletion', () => {
|
||||||
it('should return DONE if task is already executed', async () => {
|
it('should return DONE if task is already executed', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
const result = await task.waitUntilCompletion();
|
const result = await task.waitUntilCompletion();
|
||||||
|
|
||||||
expect(result).toBe('DONE');
|
expect(result).toBe('DONE');
|
||||||
@@ -240,11 +262,11 @@ describe('CancellableTask', () => {
|
|||||||
const taskPromise = new Promise<void>((resolve) => {
|
const taskPromise = new Promise<void>((resolve) => {
|
||||||
resolveTask = resolve;
|
resolveTask = resolve;
|
||||||
});
|
});
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await taskPromise;
|
await taskPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
const executePromise = task.execute(taskFn, true);
|
const executePromise = task.execute(taskFunction, true);
|
||||||
const waitPromise = task.waitUntilCompletion();
|
const waitPromise = task.waitUntilCompletion();
|
||||||
|
|
||||||
resolveTask!();
|
resolveTask!();
|
||||||
@@ -256,14 +278,14 @@ describe('CancellableTask', () => {
|
|||||||
|
|
||||||
it('should return CANCELED if task is canceled', async () => {
|
it('should return CANCELED if task is canceled', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const executePromise = task.execute(taskFn, true);
|
const executePromise = task.execute(taskFunction, true);
|
||||||
const waitPromise = task.waitUntilCompletion();
|
const waitPromise = task.waitUntilCompletion();
|
||||||
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
@@ -275,13 +297,13 @@ describe('CancellableTask', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('waitUntilExecution', () => {
|
describe('waitUntilSucceeded', () => {
|
||||||
it('should return DONE if task is already executed', async () => {
|
it('should return DONE if task is already executed', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
const result = await task.waitUntilExecution();
|
const result = await task.waitUntilSucceeded();
|
||||||
|
|
||||||
expect(result).toBe('DONE');
|
expect(result).toBe('DONE');
|
||||||
});
|
});
|
||||||
@@ -292,12 +314,12 @@ describe('CancellableTask', () => {
|
|||||||
const taskPromise = new Promise<void>((resolve) => {
|
const taskPromise = new Promise<void>((resolve) => {
|
||||||
resolveTask = resolve;
|
resolveTask = resolve;
|
||||||
});
|
});
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await taskPromise;
|
await taskPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
const executePromise = task.execute(taskFn, true);
|
const executePromise = task.execute(taskFunction, true);
|
||||||
const waitPromise = task.waitUntilExecution();
|
const waitPromise = task.waitUntilSucceeded();
|
||||||
|
|
||||||
resolveTask!();
|
resolveTask!();
|
||||||
|
|
||||||
@@ -311,7 +333,7 @@ describe('CancellableTask', () => {
|
|||||||
|
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let attempt = 0;
|
let attempt = 0;
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
attempt++;
|
attempt++;
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted && attempt === 1) {
|
if (signal.aborted && attempt === 1) {
|
||||||
@@ -320,8 +342,8 @@ describe('CancellableTask', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Start first execution
|
// Start first execution
|
||||||
const executePromise1 = task.execute(taskFn, true);
|
const executePromise1 = task.execute(taskFunction, true);
|
||||||
const waitPromise = task.waitUntilExecution();
|
const waitPromise = task.waitUntilSucceeded();
|
||||||
|
|
||||||
// Cancel the first execution
|
// Cancel the first execution
|
||||||
vi.advanceTimersByTime(10);
|
vi.advanceTimersByTime(10);
|
||||||
@@ -330,12 +352,12 @@ describe('CancellableTask', () => {
|
|||||||
await executePromise1;
|
await executePromise1;
|
||||||
|
|
||||||
// Start second execution
|
// Start second execution
|
||||||
const executePromise2 = task.execute(taskFn, true);
|
const executePromise2 = task.execute(taskFunction, true);
|
||||||
vi.advanceTimersByTime(100);
|
vi.advanceTimersByTime(100);
|
||||||
|
|
||||||
const [executeResult, waitResult] = await Promise.all([executePromise2, waitPromise]);
|
const [executeResult, waitResult] = await Promise.all([executePromise2, waitPromise]);
|
||||||
|
|
||||||
expect(executeResult).toBe('LOADED');
|
expect(executeResult).toBe('SUCCESS');
|
||||||
expect(waitResult).toBe('WAITED');
|
expect(waitResult).toBe('WAITED');
|
||||||
expect(attempt).toBe(2);
|
expect(attempt).toBe(2);
|
||||||
|
|
||||||
@@ -347,98 +369,98 @@ describe('CancellableTask', () => {
|
|||||||
it('should return ERRORED when task throws non-abort error', async () => {
|
it('should return ERRORED when task throws non-abort error', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const error = new Error('Task failed');
|
const error = new Error('Task failed');
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('ERRORED');
|
expect(result).toBe('ERRORED');
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should call errorCallback when task throws non-abort error', async () => {
|
it('should call errorCallback when task throws non-abort error', async () => {
|
||||||
const errorCallback = vi.fn();
|
const errorCallback = vi.fn();
|
||||||
const task = new CancellableTask(undefined, undefined, errorCallback);
|
const task = new CancellableTask(undefined, undefined, errorCallback);
|
||||||
const error = new Error('Task failed');
|
const error = new Error('Task failed');
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
throw error;
|
throw error;
|
||||||
};
|
};
|
||||||
|
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(errorCallback).toHaveBeenCalledTimes(1);
|
expect(errorCallback).toHaveBeenCalledTimes(1);
|
||||||
expect(errorCallback).toHaveBeenCalledWith(error);
|
expect(errorCallback).toHaveBeenCalledWith(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return CANCELED when task throws AbortError', async () => {
|
it('should return ERRORED when task throws AbortError without signal being aborted', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('CANCELED');
|
expect(result).toBe('ERRORED');
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should allow re-execution after error', async () => {
|
it('should allow re-execution after error', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn1 = async () => {
|
const taskFunction1 = async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
throw new Error('Failed');
|
throw new Error('Failed');
|
||||||
};
|
};
|
||||||
const taskFn2 = vi.fn(async () => {});
|
const taskFunction2 = vi.fn(async () => {});
|
||||||
|
|
||||||
const result1 = await task.execute(taskFn1, true);
|
const result1 = await task.execute(taskFunction1, true);
|
||||||
expect(result1).toBe('ERRORED');
|
expect(result1).toBe('ERRORED');
|
||||||
|
|
||||||
const result2 = await task.execute(taskFn2, true);
|
const result2 = await task.execute(taskFunction2, true);
|
||||||
expect(result2).toBe('LOADED');
|
expect(result2).toBe('SUCCESS');
|
||||||
expect(task.executed).toBe(true);
|
expect(task.succeeded).toBe(true);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('loading property', () => {
|
describe('running property', () => {
|
||||||
it('should return true when task is running', async () => {
|
it('should return true when task is running', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let resolveTask: () => void;
|
let resolveTask: () => void;
|
||||||
const taskPromise = new Promise<void>((resolve) => {
|
const taskPromise = new Promise<void>((resolve) => {
|
||||||
resolveTask = resolve;
|
resolveTask = resolve;
|
||||||
});
|
});
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await taskPromise;
|
await taskPromise;
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(task.loading).toBe(false);
|
expect(task.running).toBe(false);
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
expect(task.loading).toBe(true);
|
expect(task.running).toBe(true);
|
||||||
|
|
||||||
resolveTask!();
|
resolveTask!();
|
||||||
await promise;
|
await promise;
|
||||||
|
|
||||||
expect(task.loading).toBe(false);
|
expect(task.running).toBe(false);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('complete promise', () => {
|
describe('complete promise', () => {
|
||||||
it('should resolve when task completes successfully', async () => {
|
it('should resolve when task completes successfully', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = vi.fn(async () => {});
|
const taskFunction = vi.fn(async () => {});
|
||||||
|
|
||||||
const completePromise = task.complete;
|
const completePromise = task.complete;
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
await expect(completePromise).resolves.toBeUndefined();
|
await expect(completePromise).resolves.toBeUndefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should reject when task is canceled', async () => {
|
it('should reject when task is canceled', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
@@ -446,7 +468,7 @@ describe('CancellableTask', () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const completePromise = task.complete;
|
const completePromise = task.complete;
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
task.cancel();
|
task.cancel();
|
||||||
await promise;
|
await promise;
|
||||||
@@ -456,13 +478,13 @@ describe('CancellableTask', () => {
|
|||||||
|
|
||||||
it('should reject when task errors', async () => {
|
it('should reject when task errors', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async () => {
|
const taskFunction = async () => {
|
||||||
await Promise.resolve();
|
await Promise.resolve();
|
||||||
throw new Error('Failed');
|
throw new Error('Failed');
|
||||||
};
|
};
|
||||||
|
|
||||||
const completePromise = task.complete;
|
const completePromise = task.complete;
|
||||||
await task.execute(taskFn, true);
|
await task.execute(taskFunction, true);
|
||||||
|
|
||||||
await expect(completePromise).rejects.toBeUndefined();
|
await expect(completePromise).rejects.toBeUndefined();
|
||||||
});
|
});
|
||||||
@@ -472,27 +494,22 @@ describe('CancellableTask', () => {
|
|||||||
it('should automatically call abort() on signal when task is canceled', async () => {
|
it('should automatically call abort() on signal when task is canceled', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let capturedSignal: AbortSignal | null = null;
|
let capturedSignal: AbortSignal | null = null;
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
capturedSignal = signal;
|
capturedSignal = signal;
|
||||||
// Simulate a long-running task
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||||
if (signal.aborted) {
|
if (signal.aborted) {
|
||||||
throw new DOMException('Aborted', 'AbortError');
|
throw new DOMException('Aborted', 'AbortError');
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
|
|
||||||
// Wait a bit to ensure task has started
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
|
|
||||||
expect(capturedSignal).not.toBeNull();
|
expect(capturedSignal).not.toBeNull();
|
||||||
expect(capturedSignal!.aborted).toBe(false);
|
expect(capturedSignal!.aborted).toBe(false);
|
||||||
|
|
||||||
// Cancel the task
|
|
||||||
task.cancel();
|
task.cancel();
|
||||||
|
|
||||||
// Verify the signal was aborted
|
|
||||||
expect(capturedSignal!.aborted).toBe(true);
|
expect(capturedSignal!.aborted).toBe(true);
|
||||||
|
|
||||||
const result = await promise;
|
const result = await promise;
|
||||||
@@ -502,25 +519,22 @@ describe('CancellableTask', () => {
|
|||||||
it('should detect if signal was aborted after task completes', async () => {
|
it('should detect if signal was aborted after task completes', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
let controller: AbortController | null = null;
|
let controller: AbortController | null = null;
|
||||||
const taskFn = async (_: AbortSignal) => {
|
const taskFunction = async (_: AbortSignal) => {
|
||||||
// Capture the controller to abort it externally
|
// Capture the controller to abort it externally before the function returns
|
||||||
controller = task.cancelToken;
|
controller = task.abortController;
|
||||||
// Simulate some work
|
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
// Now abort before the function returns
|
|
||||||
controller?.abort();
|
controller?.abort();
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await task.execute(taskFn, true);
|
const result = await task.execute(taskFunction, true);
|
||||||
|
|
||||||
expect(result).toBe('CANCELED');
|
expect(result).toBe('CANCELED');
|
||||||
expect(task.executed).toBe(false);
|
expect(task.succeeded).toBe(false);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle abort signal in async operations', async () => {
|
it('should handle abort signal in async operations', async () => {
|
||||||
const task = new CancellableTask();
|
const task = new CancellableTask();
|
||||||
const taskFn = async (signal: AbortSignal) => {
|
const taskFunction = async (signal: AbortSignal) => {
|
||||||
// Simulate listening to abort signal during async operation
|
|
||||||
return new Promise<void>((resolve, reject) => {
|
return new Promise<void>((resolve, reject) => {
|
||||||
signal.addEventListener('abort', () => {
|
signal.addEventListener('abort', () => {
|
||||||
reject(new DOMException('Aborted', 'AbortError'));
|
reject(new DOMException('Aborted', 'AbortError'));
|
||||||
@@ -529,7 +543,7 @@ describe('CancellableTask', () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const promise = task.execute(taskFn, true);
|
const promise = task.execute(taskFunction, true);
|
||||||
await new Promise((resolve) => setTimeout(resolve, 10));
|
await new Promise((resolve) => setTimeout(resolve, 10));
|
||||||
task.cancel();
|
task.cancel();
|
||||||
|
|
||||||
|
|||||||
@@ -1,47 +1,60 @@
|
|||||||
|
/**
|
||||||
|
* A one-shot async task with cancellation support via AbortController/AbortSignal.
|
||||||
|
*
|
||||||
|
* State machine:
|
||||||
|
*
|
||||||
|
* IDLE ──execute()──▶ RUNNING ──task succeeds──▶ SUCCEEDED (terminal)
|
||||||
|
* │
|
||||||
|
* ├──cancel()/abort──▶ CANCELED ──▶ IDLE
|
||||||
|
* └──task throws─────▶ ERRORED ──▶ IDLE
|
||||||
|
*
|
||||||
|
* SUCCEEDED is terminal — further execute() calls return 'DONE'.
|
||||||
|
* Call reset() to move from SUCCEEDED back to IDLE for re-execution.
|
||||||
|
*
|
||||||
|
* execute() return values: 'SUCCESS' | 'DONE' | 'WAITED' | 'CANCELED' | 'ERRORED'
|
||||||
|
*/
|
||||||
export class CancellableTask {
|
export class CancellableTask {
|
||||||
cancelToken: AbortController | null = null;
|
abortController: AbortController | null = null;
|
||||||
cancellable: boolean = true;
|
cancellable: boolean = true;
|
||||||
/**
|
/**
|
||||||
* A promise that resolves once the bucket is loaded, and rejects if bucket is canceled.
|
* A promise that resolves once the task completes, and rejects if the task is canceled or errored.
|
||||||
*/
|
*/
|
||||||
complete!: Promise<unknown>;
|
complete!: Promise<unknown>;
|
||||||
executed: boolean = false;
|
succeeded: boolean = false;
|
||||||
|
|
||||||
private loadedSignal: (() => void) | undefined;
|
private completeResolve: (() => void) | undefined;
|
||||||
private canceledSignal: (() => void) | undefined;
|
private completeReject: (() => void) | undefined;
|
||||||
|
|
||||||
constructor(
|
constructor(
|
||||||
private loadedCallback?: () => void,
|
private succeededCallback?: () => void,
|
||||||
private canceledCallback?: () => void,
|
private canceledCallback?: () => void,
|
||||||
private errorCallback?: (error: unknown) => void,
|
private errorCallback?: (error: unknown) => void,
|
||||||
) {
|
) {
|
||||||
this.init();
|
this.init();
|
||||||
}
|
}
|
||||||
|
|
||||||
get loading() {
|
get running() {
|
||||||
return !!this.cancelToken;
|
return !!this.abortController;
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitUntilCompletion() {
|
async waitUntilCompletion() {
|
||||||
if (this.executed) {
|
if (this.succeeded) {
|
||||||
return 'DONE';
|
return 'DONE';
|
||||||
}
|
}
|
||||||
// The `complete` promise resolves when executed, rejects when canceled/errored.
|
|
||||||
try {
|
try {
|
||||||
const complete = this.complete;
|
await this.complete;
|
||||||
await complete;
|
|
||||||
return 'WAITED';
|
return 'WAITED';
|
||||||
} catch {
|
} catch {
|
||||||
// ignore
|
// expected when canceled
|
||||||
}
|
}
|
||||||
return 'CANCELED';
|
return 'CANCELED';
|
||||||
}
|
}
|
||||||
|
|
||||||
async waitUntilExecution() {
|
async waitUntilSucceeded() {
|
||||||
// Keep retrying until the task completes successfully (not canceled)
|
// Keep retrying until the task completes successfully (not canceled)
|
||||||
for (;;) {
|
for (;;) {
|
||||||
try {
|
try {
|
||||||
if (this.executed) {
|
if (this.succeeded) {
|
||||||
return 'DONE';
|
return 'DONE';
|
||||||
}
|
}
|
||||||
await this.complete;
|
await this.complete;
|
||||||
@@ -52,59 +65,60 @@ export class CancellableTask {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async execute<F extends (abortSignal: AbortSignal) => Promise<void>>(f: F, cancellable: boolean) {
|
async execute(task: (abortSignal: AbortSignal) => Promise<void>, cancellable: boolean) {
|
||||||
if (this.executed) {
|
if (this.succeeded) {
|
||||||
return 'DONE';
|
return 'DONE';
|
||||||
}
|
}
|
||||||
|
|
||||||
// if promise is pending, wait on previous request instead.
|
// if promise is pending, wait on previous request instead.
|
||||||
if (this.cancelToken) {
|
if (this.abortController) {
|
||||||
// if promise is pending, and preventCancel is requested,
|
if (!cancellable) {
|
||||||
// do not allow transition from prevent cancel to allow cancel.
|
this.cancellable = false;
|
||||||
if (this.cancellable && !cancellable) {
|
|
||||||
this.cancellable = cancellable;
|
|
||||||
}
|
}
|
||||||
await this.complete;
|
try {
|
||||||
return 'WAITED';
|
await this.complete;
|
||||||
}
|
return 'WAITED';
|
||||||
this.cancellable = cancellable;
|
} catch {
|
||||||
const cancelToken = (this.cancelToken = new AbortController());
|
|
||||||
|
|
||||||
try {
|
|
||||||
await f(cancelToken.signal);
|
|
||||||
if (cancelToken.signal.aborted) {
|
|
||||||
return 'CANCELED';
|
return 'CANCELED';
|
||||||
}
|
}
|
||||||
this.#transitionToExecuted();
|
}
|
||||||
return 'LOADED';
|
this.cancellable = cancellable;
|
||||||
|
const abortController = (this.abortController = new AbortController());
|
||||||
|
|
||||||
|
try {
|
||||||
|
await task(abortController.signal);
|
||||||
|
if (abortController.signal.aborted) {
|
||||||
|
return 'CANCELED';
|
||||||
|
}
|
||||||
|
this.#transitionToSucceeded();
|
||||||
|
return 'SUCCESS';
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
if (abortController.signal.aborted) {
|
||||||
if ((error as any).name === 'AbortError') {
|
|
||||||
// abort error is not treated as an error, but as a cancellation.
|
|
||||||
return 'CANCELED';
|
return 'CANCELED';
|
||||||
}
|
}
|
||||||
this.#transitionToErrored(error);
|
this.#transitionToErrored(error);
|
||||||
return 'ERRORED';
|
return 'ERRORED';
|
||||||
} finally {
|
} finally {
|
||||||
this.cancelToken = null;
|
if (this.abortController === abortController) {
|
||||||
|
this.abortController = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private init() {
|
private init() {
|
||||||
|
this.abortController = null;
|
||||||
|
this.succeeded = false;
|
||||||
this.complete = new Promise<void>((resolve, reject) => {
|
this.complete = new Promise<void>((resolve, reject) => {
|
||||||
this.cancelToken = null;
|
this.completeResolve = resolve;
|
||||||
this.executed = false;
|
this.completeReject = reject;
|
||||||
this.loadedSignal = resolve;
|
|
||||||
this.canceledSignal = reject;
|
|
||||||
});
|
});
|
||||||
// Suppress unhandled rejection warning
|
// Suppress unhandled rejection warning
|
||||||
this.complete.catch(() => {});
|
this.complete.catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
// will reset this job back to the initial state (isLoaded=false, no errors, etc)
|
|
||||||
async reset() {
|
async reset() {
|
||||||
this.#transitionToCancelled();
|
this.#transitionToCancelled();
|
||||||
if (this.cancelToken) {
|
if (this.abortController) {
|
||||||
await this.waitUntilCompletion();
|
await this.waitUntilCompletion();
|
||||||
}
|
}
|
||||||
this.init();
|
this.init();
|
||||||
@@ -115,27 +129,26 @@ export class CancellableTask {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#transitionToCancelled() {
|
#transitionToCancelled() {
|
||||||
if (this.executed) {
|
if (this.succeeded) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!this.cancellable) {
|
if (!this.cancellable) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this.cancelToken?.abort();
|
this.abortController?.abort();
|
||||||
this.canceledSignal?.();
|
this.completeReject?.();
|
||||||
this.init();
|
this.init();
|
||||||
this.canceledCallback?.();
|
this.canceledCallback?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
#transitionToExecuted() {
|
#transitionToSucceeded() {
|
||||||
this.executed = true;
|
this.succeeded = true;
|
||||||
this.loadedSignal?.();
|
this.completeResolve?.();
|
||||||
this.loadedCallback?.();
|
this.succeededCallback?.();
|
||||||
}
|
}
|
||||||
|
|
||||||
#transitionToErrored(error: unknown) {
|
#transitionToErrored(error: unknown) {
|
||||||
this.cancelToken = null;
|
this.completeReject?.();
|
||||||
this.canceledSignal?.();
|
|
||||||
this.init();
|
this.init();
|
||||||
this.errorCallback?.(error);
|
this.errorCallback?.(error);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user