diff --git a/src/ScheduledTask.ts b/src/ScheduledTask.ts index e835337..9d9f983 100644 --- a/src/ScheduledTask.ts +++ b/src/ScheduledTask.ts @@ -3,6 +3,7 @@ import type SchedulingTask from './SchedulingTask' type ScheduledTask = SchedulingTask & { promise: Promise resolve: () => void + reject: (reason: DOMException) => void } export default ScheduledTask diff --git a/src/SchedulingTask.ts b/src/SchedulingTask.ts index 03b97ee..67bb64f 100644 --- a/src/SchedulingTask.ts +++ b/src/SchedulingTask.ts @@ -2,4 +2,5 @@ export default interface SchedulingTask { type: 'frame-based' | 'idle-based' workTime: number priority: number + signal?: AbortSignal } diff --git a/src/ThreadScheduler.ts b/src/ThreadScheduler.ts index d826d97..e6ce91e 100644 --- a/src/ThreadScheduler.ts +++ b/src/ThreadScheduler.ts @@ -23,7 +23,17 @@ class ThreadScheduler { schedule(task: SchedulingTask): ScheduledTask { const scheduled = { ...task, ...withResolvers() } + this.#insertTask(scheduled) + task.signal?.addEventListener( + 'abort', + () => { + this.#removeTask(scheduled) + scheduled.reject(new DOMException('The operation was aborted.', 'AbortError')) + }, + { once: true }, + ) + return scheduled } diff --git a/src/isTimeToYield.ts b/src/isTimeToYield.ts index a266ce9..509f8b6 100644 --- a/src/isTimeToYield.ts +++ b/src/isTimeToYield.ts @@ -1,7 +1,7 @@ import hasValidContext from './utils/hasValidContext' import SchedulingStrategy from './SchedulingStrategy' import threadScheduler from './ThreadScheduler' -import strategyToTask from './utils/strategyToTask' +import toTask from './utils/toTask' // #performance // calling `isTimeToYield()` thousand of times is slow @@ -34,7 +34,7 @@ export default function isTimeToYield(strategy: SchedulingStrategy = 'smooth'): } cache.lastCallTime = now - cache.lastResult = threadScheduler.isTimeToYield(strategyToTask(strategy)) + cache.lastResult = threadScheduler.isTimeToYield(toTask(strategy)) return cache.lastResult } diff --git a/src/utils/strategyToTask.ts b/src/utils/toTask.ts similarity index 71% rename from src/utils/strategyToTask.ts rename to src/utils/toTask.ts index a3c7c71..64d23e8 100644 --- a/src/utils/strategyToTask.ts +++ b/src/utils/toTask.ts @@ -1,7 +1,10 @@ import type SchedulingStrategy from '../SchedulingStrategy' import type SchedulingTask from '../SchedulingTask' -export default function strategyToTask(schedulingStrategy: SchedulingStrategy): SchedulingTask { +export default function toTask( + schedulingStrategy: SchedulingStrategy, + signal?: AbortSignal, +): SchedulingTask { const options: Record = { interactive: { type: 'frame-based', @@ -19,5 +22,7 @@ export default function strategyToTask(schedulingStrategy: SchedulingStrategy): priority: 10, }, } - return options[schedulingStrategy] + const task = options[schedulingStrategy] + task.signal = signal + return task } diff --git a/src/yieldControl.ts b/src/yieldControl.ts index 6aa3e36..cc98a1e 100644 --- a/src/yieldControl.ts +++ b/src/yieldControl.ts @@ -1,7 +1,7 @@ import hasValidContext from './utils/hasValidContext' import SchedulingStrategy from './SchedulingStrategy' import threadScheduler from './ThreadScheduler' -import strategyToTask from './utils/strategyToTask' +import toTask from './utils/toTask' /** * Waits for the browser to become idle again in order to resume work. Calling `yieldControl()` @@ -13,10 +13,13 @@ import strategyToTask from './utils/strategyToTask' * resolved second. * @returns {Promise} A promise that gets resolved when the work can continue. */ -export default async function yieldControl(strategy: SchedulingStrategy = 'smooth'): Promise { +export default async function yieldControl( + strategy: SchedulingStrategy = 'smooth', + signal?: AbortSignal, +): Promise { if (!hasValidContext()) { return } - return threadScheduler.schedule(strategyToTask(strategy)).promise + return threadScheduler.schedule(toTask(strategy, signal)).promise } diff --git a/src/yieldOrContinue.ts b/src/yieldOrContinue.ts index e812cd9..80ad911 100644 --- a/src/yieldOrContinue.ts +++ b/src/yieldOrContinue.ts @@ -16,9 +16,12 @@ import SchedulingStrategy from './SchedulingStrategy' */ // disabling ESLint otherwise `requestPromiseEscape()` in `yieldControl()` won't work // eslint-disable-next-line @typescript-eslint/promise-function-async -export default function yieldOrContinue(priority: SchedulingStrategy = 'smooth'): Promise { - if (isTimeToYield(priority)) { - return yieldControl(priority) +export default function yieldOrContinue( + priority: SchedulingStrategy = 'smooth', + signal?: AbortSignal, +): Promise { + if (signal?.aborted !== true && isTimeToYield(priority)) { + return yieldControl(priority, signal) } return Promise.resolve()