Skip to content

Commit

Permalink
signal option for canceling scheduled tasks, resolve #4
Browse files Browse the repository at this point in the history
  • Loading branch information
astoilkov committed Mar 1, 2024
1 parent 8a32210 commit 6c2a5aa
Show file tree
Hide file tree
Showing 7 changed files with 33 additions and 10 deletions.
1 change: 1 addition & 0 deletions src/ScheduledTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type SchedulingTask from './SchedulingTask'
type ScheduledTask = SchedulingTask & {
promise: Promise<void>
resolve: () => void
reject: (reason: DOMException) => void
}

export default ScheduledTask
1 change: 1 addition & 0 deletions src/SchedulingTask.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,5 @@ export default interface SchedulingTask {
type: 'frame-based' | 'idle-based'
workTime: number
priority: number
signal?: AbortSignal
}
10 changes: 10 additions & 0 deletions src/ThreadScheduler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
}

Expand Down
4 changes: 2 additions & 2 deletions src/isTimeToYield.ts
Original file line number Diff line number Diff line change
@@ -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
Expand Down Expand Up @@ -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
}
9 changes: 7 additions & 2 deletions src/utils/strategyToTask.ts → src/utils/toTask.ts
Original file line number Diff line number Diff line change
@@ -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<SchedulingStrategy, SchedulingTask> = {
interactive: {
type: 'frame-based',
Expand All @@ -19,5 +22,7 @@ export default function strategyToTask(schedulingStrategy: SchedulingStrategy):
priority: 10,
},
}
return options[schedulingStrategy]
const task = options[schedulingStrategy]
task.signal = signal
return task
}
9 changes: 6 additions & 3 deletions src/yieldControl.ts
Original file line number Diff line number Diff line change
@@ -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()`
Expand All @@ -13,10 +13,13 @@ import strategyToTask from './utils/strategyToTask'
* resolved second.
* @returns {Promise<void>} A promise that gets resolved when the work can continue.
*/
export default async function yieldControl(strategy: SchedulingStrategy = 'smooth'): Promise<void> {
export default async function yieldControl(
strategy: SchedulingStrategy = 'smooth',
signal?: AbortSignal,
): Promise<void> {
if (!hasValidContext()) {
return
}

return threadScheduler.schedule(strategyToTask(strategy)).promise
return threadScheduler.schedule(toTask(strategy, signal)).promise
}
9 changes: 6 additions & 3 deletions src/yieldOrContinue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
if (isTimeToYield(priority)) {
return yieldControl(priority)
export default function yieldOrContinue(
priority: SchedulingStrategy = 'smooth',
signal?: AbortSignal,
): Promise<void> {
if (signal?.aborted !== true && isTimeToYield(priority)) {
return yieldControl(priority, signal)
}

return Promise.resolve()
Expand Down

0 comments on commit 6c2a5aa

Please sign in to comment.