mirror of
https://github.com/zhw2590582/ArtPlayer.git
synced 2026-10-26 08:51:20 -08:00
refactor(core): CORE-03 add typed resource scopes and cleanup adapters
This commit is contained in:
1 parent
362c616998
commit
3e38dd0d19
12 files changed
+1242
-6
No files matched your search
@@ -0,0 +1,85 @@
|
||||
import type ResourceScope from './scope'
|
||||
|
||||
export function listen(scope: ResourceScope, target: EventTarget, name: string, callback: EventListenerOrEventListenerObject, options: AddEventListenerOptions = {}): () => void {
|
||||
const { capture = false, once = false, signal } = options
|
||||
if (scope.closed || signal?.aborted)
|
||||
return () => {}
|
||||
let release = () => {}
|
||||
function listener(this: EventTarget, event: Event) {
|
||||
if (once)
|
||||
release()
|
||||
if (scope.closed)
|
||||
return
|
||||
if (typeof callback === 'function')
|
||||
callback.call(this, event)
|
||||
else
|
||||
callback.handleEvent(event)
|
||||
}
|
||||
target.addEventListener(name, listener, options)
|
||||
release = scope.add(() => {
|
||||
try {
|
||||
target.removeEventListener(name, listener, capture)
|
||||
}
|
||||
finally {
|
||||
signal?.removeEventListener('abort', release)
|
||||
}
|
||||
})
|
||||
signal?.addEventListener('abort', release, { once: true })
|
||||
return release
|
||||
}
|
||||
|
||||
export function timeout(scope: ResourceScope, callback: () => void, delay: number): () => void {
|
||||
if (scope.closed)
|
||||
return () => {}
|
||||
let pending = true
|
||||
let release = () => {}
|
||||
const timer = setTimeout(() => {
|
||||
if (!pending)
|
||||
return
|
||||
release()
|
||||
if (!scope.closed)
|
||||
callback()
|
||||
}, delay)
|
||||
release = scope.add(() => {
|
||||
pending = false
|
||||
clearTimeout(timer)
|
||||
})
|
||||
return release
|
||||
}
|
||||
|
||||
export function animationFrame(scope: ResourceScope, callback: FrameRequestCallback): () => void {
|
||||
if (scope.closed)
|
||||
return () => {}
|
||||
let pending = true
|
||||
let release = () => {}
|
||||
const frame = requestAnimationFrame((time) => {
|
||||
if (!pending)
|
||||
return
|
||||
release()
|
||||
if (!scope.closed)
|
||||
callback(time)
|
||||
})
|
||||
release = scope.add(() => {
|
||||
pending = false
|
||||
cancelAnimationFrame(frame)
|
||||
})
|
||||
return release
|
||||
}
|
||||
|
||||
export function requestController(scope: ResourceScope): AbortController | undefined {
|
||||
if (typeof AbortController === 'undefined')
|
||||
return undefined
|
||||
const controller = new AbortController()
|
||||
scope.add(() => {
|
||||
controller.abort()
|
||||
})
|
||||
return controller
|
||||
}
|
||||
|
||||
export function objectURL(scope: ResourceScope, blob: Blob): string {
|
||||
const url = URL.createObjectURL(blob)
|
||||
scope.add(() => {
|
||||
URL.revokeObjectURL(url)
|
||||
})
|
||||
return url
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
export type Cleanup = () => undefined
|
||||
|
||||
export class ResourceCleanupError extends Error {
|
||||
constructor(readonly errors: unknown[]) {
|
||||
super('Failed to release ArtPlayer resources')
|
||||
this.name = 'ResourceCleanupError'
|
||||
}
|
||||
}
|
||||
|
||||
export default class ResourceScope {
|
||||
private cleanups = new Set<() => void>()
|
||||
private disposed = false
|
||||
|
||||
get closed(): boolean {
|
||||
return this.disposed
|
||||
}
|
||||
|
||||
add(cleanup: Cleanup): () => void {
|
||||
let active = true
|
||||
const release = () => {
|
||||
if (!active)
|
||||
return
|
||||
active = false
|
||||
this.cleanups.delete(release)
|
||||
cleanup()
|
||||
}
|
||||
if (this.closed)
|
||||
release()
|
||||
else
|
||||
this.cleanups.add(release)
|
||||
return release
|
||||
}
|
||||
|
||||
child(): ResourceScope {
|
||||
const child = new ResourceScope()
|
||||
const release = this.add(() => {
|
||||
child.dispose()
|
||||
})
|
||||
child.add(() => {
|
||||
release()
|
||||
})
|
||||
return child
|
||||
}
|
||||
|
||||
dispose(): void {
|
||||
if (this.closed)
|
||||
return
|
||||
this.disposed = true
|
||||
const errors: unknown[] = []
|
||||
// Unwind dependencies; release removes each registration before invoking it.
|
||||
for (const release of Array.from(this.cleanups).reverse()) {
|
||||
try {
|
||||
release()
|
||||
}
|
||||
catch (error) {
|
||||
if (error instanceof ResourceCleanupError)
|
||||
errors.push(...error.errors)
|
||||
else
|
||||
errors.push(error)
|
||||
}
|
||||
}
|
||||
if (errors.length)
|
||||
throw new ResourceCleanupError(errors)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user