-
-
Notifications
You must be signed in to change notification settings - Fork 278
feat: Add subscribeOnce and waitUntil methods
#8575
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
base: main
Are you sure you want to change the base?
Changes from all commits
c460303
c2bffe6
f84cf14
580d0ac
5907ef9
b58d02b
6200261
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -656,6 +656,161 @@ export class Messenger< | |
| subscribers.set(handler, metadata); | ||
| } | ||
|
|
||
| /** | ||
| * Subscribe to an event, with a selector, invoking the handler exactly once. | ||
| * | ||
| * Registers the given handler function as an event handler for the given | ||
| * event type. When an event is published, its payload is first passed to the | ||
| * selector. The event handler is only called if the selector's return value | ||
| * differs from its last known return value. Additionally if the optional condition | ||
| * function is provided, it is checked whether it returns `true`. | ||
| * The handler is invoked at most once, after which the subscription is removed. | ||
| * | ||
| * @param eventType - The event type. This is a unique identifier for this event. | ||
| * @param handler - The event handler. The type of the parameters for this event | ||
| * handler must match the return type of the selector. | ||
| * @param options - Options bag. | ||
| * @param options.selector - The selector function used to select relevant data | ||
| * from the event payload. The type of the parameters for this selector must | ||
| * match the type of the payload for this event type. | ||
| * @param options.condition - An optional predicate evaluated against the | ||
| * selector's return value. The handler is only invoked when this returns `true`. | ||
| * @template EventType - A type union of Event type strings. | ||
| * @template SelectorReturnValue - The selector return value. | ||
| */ | ||
| subscribeOnce<EventType extends Event['type'], SelectorReturnValue>( | ||
| eventType: EventType, | ||
| handler: SelectorEventHandler<SelectorReturnValue>, | ||
| options: { | ||
| selector: SelectorFunction<Event, EventType, SelectorReturnValue>; | ||
| condition?: (value: SelectorReturnValue) => boolean; | ||
| }, | ||
| ): void; | ||
|
|
||
| /** | ||
| * Subscribe to an event, invoking the handler exactly once. | ||
| * | ||
| * Registers the given function as an event handler for the given event type | ||
| * and automatically unsubscribes after the first invocation. | ||
| * | ||
| * If `options.condition` is provided, the handler is only invoked (and the | ||
| * subscription only removed) when the condition returns `true`. | ||
| * | ||
| * @param eventType - The event type. This is a unique identifier for this event. | ||
| * @param handler - The event handler. The type of the parameters for this event | ||
| * handler must match the type of the payload for this event type. | ||
| * @param options - Options bag. | ||
| * @param options.condition - A predicate evaluated against the event payload. | ||
| * The handler is only invoked when this returns `true`. | ||
| * @template EventType - A type union of Event type strings. | ||
| */ | ||
| subscribeOnce<EventType extends Event['type']>( | ||
| eventType: EventType, | ||
| handler: ExtractEventHandler<Event, EventType>, | ||
| options?: { | ||
| condition?: ( | ||
| ...payload: ExtractEventPayload<Event, EventType> | ||
| ) => boolean; | ||
| }, | ||
| ): void; | ||
|
|
||
| subscribeOnce<EventType extends Event['type'], SelectorReturnValue>( | ||
| eventType: EventType, | ||
| handler: | ||
| | ExtractEventHandler<Event, EventType> | ||
| | SelectorEventHandler<SelectorReturnValue>, | ||
| options?: { | ||
| selector?: SelectorFunction<Event, EventType, SelectorReturnValue>; | ||
| condition?: | ||
| | ((...payload: ExtractEventPayload<Event, EventType>) => boolean) | ||
| | ((value: SelectorReturnValue) => boolean); | ||
| }, | ||
| ): void { | ||
| const { selector, condition } = options ?? {}; | ||
| // Casting to unknown to handle both the code path where a selector is defined and where it is omitted. | ||
| const internalHandler = (...args: unknown[]): void => { | ||
| if ( | ||
| condition && | ||
| !(condition as (...args: unknown[]) => boolean)(...args) | ||
| ) { | ||
| return; | ||
| } | ||
| (handler as (...args: unknown[]) => void)(...args); | ||
| this.unsubscribe(eventType, internalHandler); | ||
| }; | ||
|
|
||
| this.subscribe( | ||
| eventType, | ||
| internalHandler, | ||
| selector as SelectorFunction<Event, EventType, SelectorReturnValue>, | ||
| ); | ||
| } | ||
|
|
||
| /** | ||
| * Return a promise that resolves the next time the selector's return value | ||
| * changes and, if provided, the `options.condition` predicate returns `true`. | ||
| * | ||
| * @param eventType - The event type. This is a unique identifier for this event. | ||
| * @param options - Options bag. | ||
| * @param options.selector - The selector function used to select relevant data | ||
| * from the event payload. | ||
| * @param options.condition - An optional predicate evaluated against the | ||
| * selector's return value. The promise only resolves when this returns `true`. | ||
| * @template EventType - A type union of Event type strings. | ||
| * @template SelectorReturnValue - The selector return value. | ||
| * @returns A promise that resolves with the selector's return value. | ||
| */ | ||
| waitUntil<EventType extends Event['type'], SelectorReturnValue>( | ||
| eventType: EventType, | ||
| options: { | ||
| selector: SelectorFunction<Event, EventType, SelectorReturnValue>; | ||
| condition?: (value: SelectorReturnValue) => boolean; | ||
| }, | ||
| ): Promise<SelectorReturnValue>; | ||
|
|
||
| /** | ||
| * Return a promise that resolves the next time the given event is published. | ||
| * | ||
| * If `options.condition` is provided, the promise only resolves when the | ||
| * condition returns `true`. | ||
| * | ||
| * @param eventType - The event type. This is a unique identifier for this event. | ||
| * @param options - Options bag. | ||
| * @param options.condition - A predicate evaluated against the event payload. | ||
| * The promise only resolves when this returns `true`. | ||
| * @template EventType - A type union of Event type strings. | ||
| * @returns A promise that resolves with the event payload's first argument. | ||
| */ | ||
| waitUntil<EventType extends Event['type']>( | ||
| eventType: EventType, | ||
| options?: { | ||
| condition?: ( | ||
| ...payload: ExtractEventPayload<Event, EventType> | ||
| ) => boolean; | ||
| }, | ||
| ): Promise<ExtractEventPayload<Event, EventType>[0]>; | ||
|
|
||
| waitUntil<EventType extends Event['type'], SelectorReturnValue>( | ||
| eventType: EventType, | ||
| options?: { | ||
| selector?: SelectorFunction<Event, EventType, SelectorReturnValue>; | ||
| condition?: | ||
| | ((...payload: ExtractEventPayload<Event, EventType>) => boolean) | ||
| | ((value: SelectorReturnValue) => boolean); | ||
| }, | ||
| ): Promise<SelectorReturnValue | ExtractEventPayload<Event, EventType>[0]> { | ||
| return new Promise((resolve) => { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How we expect getUnlockPromise: () => {
if (engineContext.KeyringController.isUnlocked()) {
return Promise.resolve();
}
return new Promise<void>((resolve) => {
controllerMessenger.subscribeOnceIf(
'KeyringController:unlock',
resolve,
() => true,
);
});
},Is the idea that with this method you could shorten this to the following? getUnlockPromise: () => {
return await controllerMessenger.waitUntil(
'KeyringController:unlock',
{
condition: () => {
return engineContext.KeyringController.isUnlocked();
}
}
);
},Or is it that we would expect people to write this instead? getUnlockPromise: () => {
return await controllerMessenger.waitUntil(
'KeyringController:stateChange',
{
selector: (state) => state.isUnlocked,
}
);
},
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Either way would we want to check the condition beforehand to make sure is not true before subscribing? (I guess the same goes for |
||
| this.subscribeOnce( | ||
| eventType, | ||
| resolve as SelectorEventHandler<SelectorReturnValue>, | ||
| options as { | ||
| selector: SelectorFunction<Event, EventType, SelectorReturnValue>; | ||
| condition?: (value: SelectorReturnValue) => boolean; | ||
| }, | ||
| ); | ||
| }); | ||
| } | ||
|
|
||
| /** | ||
| * Unsubscribe from an event. | ||
| * | ||
|
|
||
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.
When not using a selector this just returns the first parameter of the event. The other option is returning the entire array, but that feels a bit complicated 🤔
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.
Hmm, why does it feel complicated? If it's easy to support, I say we give consumers the whole payload.