const dropId: unique symbol = Symbol("id") export type DropRef = { [dropId]: number } export abstract class Drop { private static weak: { [id: number]: Drop } = {} private static registry = new FinalizationRegistry((id: number) => { const weak = Drop.weak[id] if (weak) weak.drop() }) private static idCtr: number = 0 private dropId?: number private dropRef?: DropRef | WeakRef protected constructor() { this.dropId = Drop.idCtr++ this.dropRef = { [dropId]: this.dropId } const weak = this.weak() Drop.weak[this.dropId] = weak Drop.registry.register(this.dropRef, this.dropId, this.dropRef) return new Proxy(this, { set(target: any, prop, value) { if (prop === "dropRef" || prop == "dropId") return false target[prop] = value ;(weak as any)[prop] = value return true }, }) } protected register() {} private weak(): this { const weak = Object.assign(Object.create(Object.getPrototypeOf(this)), this) if (this.dropRef) weak.ref = new WeakRef(this.dropRef) return weak } abstract onDrop(): void drop(): void { if (!this.dropRef || !this.dropId) return this.onDrop() this.leak() } leak(): this { if (!this.dropRef || !this.dropId) return this Drop.registry.unregister(this.dropRef) delete Drop.weak[this.dropId] delete this.dropRef delete this.dropId return this } } export class DropPromise implements Promise { private static dropFns: { [id: number]: () => void } = {} private static registry = new FinalizationRegistry((id: number) => { const drop = DropPromise.dropFns[id] if (drop) { drop() delete DropPromise.dropFns[id] } }) private static idCtr: number = 0 private dropId: number private dropRef: DropRef; [Symbol.toStringTag] = "DropPromise" private constructor( private readonly promise: Promise, dropFnOrRef?: (() => void) | DropRef, ) { if (dropFnOrRef && dropId in dropFnOrRef) { this.dropId = dropFnOrRef[dropId] this.dropRef = dropFnOrRef return } this.dropId = DropPromise.idCtr++ this.dropRef = { [dropId]: this.dropId } if (dropFnOrRef) DropPromise.dropFns[this.dropId] = dropFnOrRef DropPromise.registry.register(this.dropRef, this.dropId, this.dropRef) } static of(promise: Promise, dropFn?: () => void): DropPromise { return new DropPromise(promise, dropFn) } static ref(promise: Promise, dropRef: DropRef): DropPromise { return new DropPromise(promise, dropRef) } then( onfulfilled?: | ((value: T) => TResult1 | PromiseLike) | null | undefined, onrejected?: | ((reason: any) => TResult2 | PromiseLike) | null | undefined, ): Promise { return DropPromise.ref( this.promise.then(onfulfilled, onrejected), this.dropRef, ) } catch( onrejected?: | ((reason: any) => TResult | PromiseLike) | null | undefined, ): Promise { return DropPromise.ref(this.promise.catch(onrejected), this.dropRef) } finally(onfinally?: (() => void) | null | undefined): Promise { return DropPromise.ref(this.promise.finally(onfinally), this.dropRef) } } export class DropGenerator implements AsyncGenerator { private static dropFns: { [id: number]: () => void } = {} private static registry = new FinalizationRegistry((id: number) => { const drop = DropGenerator.dropFns[id] if (drop) { drop() delete DropGenerator.dropFns[id] } }) private static idCtr: number = 0 private dropId: number private dropRef: DropRef; [Symbol.asyncIterator] = () => this private constructor( private readonly generator: AsyncGenerator, dropFn?: () => void, ) { this.dropId = DropGenerator.idCtr++ this.dropRef = { [dropId]: this.dropId } if (dropFn) DropGenerator.dropFns[this.dropId] = dropFn DropGenerator.registry.register(this.dropRef, this.dropId, this.dropRef) } static of( generator: AsyncGenerator, dropFn?: () => void, ): DropGenerator { return new DropGenerator(generator, dropFn) } next(...args: [] | [TNext]): Promise> { return DropPromise.ref(this.generator.next(...args), this.dropRef) } return( value: TReturn | PromiseLike, ): Promise> { return DropPromise.ref(this.generator.return(value), this.dropRef) } throw(e: any): Promise> { return DropPromise.ref(this.generator.throw(e), this.dropRef) } }