-
Notifications
You must be signed in to change notification settings - Fork 12.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add definitions for WeakRef and FinalizationRegistry #38232
Conversation
Great, this looks good to me:
The file naming fits too, thanks 👍 |
I'll give Nathan a chance to look over it, but I'm good to merge |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Questions about the type of target
and unregisterToken
:
- Maybe they should be
unknown
or{}
instead. - The proposal uses 'value' and 'object' fairly consistently, but doesn't define them. Does the ecmascript spec do so?
src/lib/esnext.weakref.d.ts
Outdated
* @param callback If given, the registry uses the given callback instead of the one it was | ||
* created with. | ||
*/ | ||
cleanupSome(callback?: (heldValue: any) => void): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
it's weird that heldValue: any
but target: object
and unregisterToken: object
. I know the proposal uses the terms 'value' and 'object' but I don't think they exactly match the type system's definitions. In particular, object
means "non-primitive". Can you provide null
, undefined
, "foo"
, false
or 3
for target
or unregisterToken
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
-
target
must be an object, i.e. non-primitive type.
Specification: https://tc39.es/proposal-weakrefs/#sec-weak-ref-target- If Type(target) is not Object, throw a TypeError exception.
V8 implementation behavior:
$ node --harmony-weak-refs Welcome to Node.js v14.0.0. Type ".help" for more information. > new WeakRef(null) Uncaught TypeError: WeakRef: target must be an object at new WeakRef (<anonymous>) > new WeakRef(undefined) Uncaught TypeError: WeakRef: target must be an object at new WeakRef (<anonymous>) > new WeakRef(1) Uncaught TypeError: WeakRef: target must be an object at new WeakRef (<anonymous>) > new WeakRef("str") Uncaught TypeError: WeakRef: target must be an object at new WeakRef (<anonymous>)
-
unregisterToken
must be an object (non-primitive type), or undefined.Specification: https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.register
- If Type(unregisterToken) is not Object,
a. If unregisterToken is not undefined, throw a TypeError exception.
b. Set unregisterToken to empty.
V8 implementation behavior (please note that v8 in Node.js still uses old specification name FinalizationGroup instead of FinalizationRegistry):
$ node --harmony-weak-refs > var fg = new FinalizationGroup(() => {}) undefined > fg.register({}, null, null) Uncaught TypeError: unregisterToken ('null') must be an object at FinalizationGroup.register (<anonymous>) > fg.register({}, null, undefined) undefined > fg.register({}, null, 1) Uncaught TypeError: unregisterToken ('1') must be an object at FinalizationGroup.register (<anonymous>) > fg.register({}, null, 'a') Uncaught TypeError: unregisterToken ('a') must be an object at FinalizationGroup.register (<anonymous>) > fg.register({}, null, false) Uncaught TypeError: unregisterToken ('false') must be an object at FinalizationGroup.register (<anonymous>)
- If Type(unregisterToken) is not Object,
-
heldValue
can have any type.Specification: https://tc39.es/proposal-weakrefs/#sec-finalization-registry.prototype.register
V8 implementation behavior:$ node --harmony-weak-refs > var fg = new FinalizationGroup(() => {}) undefined > fg.register({}, null) undefined > fg.register({}, false) undefined > fg.register({}, 1) undefined > fg.register({}, {}) undefined
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd suggest omitting cleanupSome from .d.ts, or otherwise somehow mark it as dispreferred/not tab-complete it. It's "normative optional" in the specification, it's currently under some debate, and it's not going to be part of what V8/Chrome ships initially. See whatwg/html#5446 for context.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's "normative optional" in the specification, it can be marked as optional in .d.ts. Anyway this declaration is available only under --lib esnext
compiler flag, which means that it isn't stable. If in final stage of the proposal this method will be removed, corresponding TypeScript declaration should reflect this change before move to stable es2020
lib.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Chrome's not going to ship with it initially, let's make this optional to start so that people have to prove that it's available before using it. (Only when strict: true
of course.)
cleanupSome(callback?: (heldValue: any) => void): void; | |
cleanupSome?(callback?: (heldValue: any) => void): void; |
Actually this behavior is the same as in TypeScript/lib/lib.es2015.collection.d.ts Line 45 in 0326534
|
src/lib/esnext.weakref.d.ts
Outdated
* @param callback If given, the registry uses the given callback instead of the one it was | ||
* created with. | ||
*/ | ||
cleanupSome(callback?: (heldValue: any) => void): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If Chrome's not going to ship with it initially, let's make this optional to start so that people have to prove that it's available before using it. (Only when strict: true
of course.)
cleanupSome(callback?: (heldValue: any) => void): void; | |
cleanupSome?(callback?: (heldValue: any) => void): void; |
Is there any reason |
cleanupSome has been moved into a separate Stage 2 proposal, so I recommend omitting it from the typings here. |
What would you include in the Generic?
I see a lot more value in the latter two, but in many cases you might only use one or the other. It doesn't seem obvious to me what the Generic implementation would be. |
I've generally defined it with all 3 as: declare global {
class FinalizationRegistry<
TValue extends object=object,
THeldValue=any,
TUnregisterToken extends object=never
> {
constructor(
finalizationCallback: (heldValue: THeldValue) => void,
);
register(
object: TValue,
heldValue: THeldValue,
unregisterToken?: TUnregisterToken,
): void;
unregister(unregisterToken: TUnregisterToken): void;
}
} I do agree you'd often only use some, it's a bit of a shame there aren't named generics. There are hacky ways around it (like this) but this isn't an existing convention in the TS standard lib. type FinalizationOptions = {
Value: object,
HeldValue: any,
UnregisterToken: object,
}
type Get<T extends object, K, Else> = K extends keyof T ? T[K] : Else;
declare class FinalizationRegistry<T extends Partial<FinalizationOptions>> {
constructor(
finalizationCallback: (heldValue: Get<T, 'HeldValue', any>) => void,
);
register(
value: Get<T, 'Value', object>,
heldValue: Get<T, 'HeldValue', any>,
unregisterToken?: Get<T, 'UnregisterToken', never>,
): void;
unregister(
unregisterToken: Get<T, 'UnregisterToken', never>,
): void;
}
const f = new FinalizationRegistry<{ HeldValue: number }>((i /* number */) => {
console.log(i);
});
f.register({} /* object */, 3 /* number */); |
deref(): T | undefined; | ||
} | ||
|
||
interface WeakRefConstructor { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hi, any chance to expedite this change?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, it got lost in my email, but I think it's good to go after I re-run the github CI.
Funny coincidence, i just started using weak refs/Finalisation registry in a project yesterday and was little annoyed that it wasn't typed (the typescript nightly i had installed was a few days old). Perfect timing. Two notes on FinalisationRegistry: As for Generics, this is what i used:
declare class FinalizationRegistry<T extends object, V = void, Token extends object = object> {
constructor (callback: (heldValue: V) => void)
register (target: T, heldValue: V, token?: Token): void
unregister (token: Token): void
} usage: const registry = new FinalizationRegistry<Record<string, string>>(() => console.log('some cleanup optimisation'))
// => `FinalizationRegistry<Record<string, string>, void, object>` const registry = new FinalizationRegistry<readonly string[], number, readonly string[]>((heldItem: number) => console.log(`Array of ${heldItem} hasn't been unregistered`'))
const arr = [1,2,3,4] as const
registry.register(arr, arr.length, arr) class Collection<T extends object, Held = void> {
#registry: FinalizationRegistry<T, Held, this>
constructor (callback: (heldValue: Held) => void, iterable?: Iterable<[T, Held]>) {
this.#registry = new (FinalizationRegistry)(callback)
for (const [item, held] of iterable ?? []) this.#registry.register(item, held, this)
}
register (item: T, heldValue: Held) { this.#registry.register(item, heldValue, this) }
reset () { this.#registry.unregister(this) }
} How @Jamesernator did the |
* Creates a WeakRef instance for the given target object. | ||
* @param target The target object for the WeakRef instance. | ||
*/ | ||
new<T extends object>(target?: T): WeakRef<T>; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It’s invalid to call WeakRef
with undefined
:
new<T extends object>(target?: T): WeakRef<T>; | |
new<T extends object>(target: T): WeakRef<T>; |
I’m fixing this in #42274.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
||
declare var WeakRef: WeakRefConstructor; | ||
|
||
interface FinalizationRegistry { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
interface FinalizationRegistry { | |
interface FinalizationRegistry<T> { |
* object. If provided (and not undefined), this must be an object. If not provided, the target | ||
* cannot be unregistered. | ||
*/ | ||
register(target: object, heldValue: any, unregisterToken?: object): void; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
register(target: object, heldValue: any, unregisterToken?: object): void; | |
register(target: object, heldValue: T, unregisterToken?: object): void; |
} | ||
|
||
interface FinalizationRegistryConstructor { | ||
readonly prototype: FinalizationRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
readonly prototype: FinalizationRegistry; | |
readonly prototype: FinalizationRegistry<any>; |
* Creates a finalization registry with an associated cleanup callback | ||
* @param cleanupCallback The callback to call after an object in the registry has been reclaimed. | ||
*/ | ||
new(cleanupCallback: (heldValue: any) => void): FinalizationRegistry; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
new(cleanupCallback: (heldValue: any) => void): FinalizationRegistry; | |
new<T>(cleanupCallback: (heldValue: T) => void): FinalizationRegistry<T>; |
Fixes #32393
Link to proposal https://github.com/tc39/proposal-weakrefs/blob/master/reference.md