-
-
Notifications
You must be signed in to change notification settings - Fork 183
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
Feature: Add selector subscriptions #551
Conversation
src/ControllerMessenger.ts
Outdated
for (const [handler, selector] of subscribers.entries()) { | ||
if (selector) { | ||
const newValue = selector(...payload); | ||
|
||
if (newValue !== this.eventPayloadCache.get(handler)) { | ||
this.eventPayloadCache.set(handler, newValue); | ||
handler(newValue); | ||
} | ||
} else { | ||
handler(...payload); | ||
} |
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.
This is a pre-existing issue so I'm disinclined to deal with it here, but, if a handler or selector throws, any remaining handlers will not be called. Should we somehow catch errors from handlers and/or selectors?
cc: @Gudahtt
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 suppose we could just repeat the trick we use in safe-event-emitter
: https://github.com/MetaMask/safe-event-emitter/blob/faf5620ac17ac6d32d8dd00ba0c3ffd9247ee6cf/index.ts/#L8-L17
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.
Ref: #566
e44ebcb
to
ee67ca9
Compare
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.
LGTM!
This non-breaking PR adds [`reselect`](https://npmjs.com/package/reselect)-style selectors as an optional parameter to controller messenger event subscriptions. We call subscriptions with selectors **selector subscriptions**. This enables subscribing to subsets of large event payloads, for example `*:stateChange` events. ```typescript // ControllerMessenger.subscribe selector signature subscribe<E extends Event['type'], V>( eventType: E, handler: (nextValue: V, previousValue: V | undefined) => void, selector: (payload: ExtractEventPayload<Event, E>) => V, ): void; ``` When a selector subscription is triggered, the corresponding selector is passed the entire event payload, and returns a single value. If the value has changed since the last occurrence of the event, the handler is called with the new value and the previous value, or `undefined` if no previous value exists. Since controller state is immutable, the event handler can efficiently diff the selector return values and trigger other logic dependent on changes to subsets of complex event payloads (e.g., other events). This feature is motivated by our frequent need to subscribe to controller substate(s). Every controller has a generic `*:stateChange` event, but parsing that state is often error-prone and/or annoying. By adding selector subscriptions, we can performantly parse complex controller state in one place, and emit more specific events as necessary. This implementation was chosen due to our existing widespread use of selectors in our UIs, and in order to avoid the introduction of querying DSLs such as JSONPath and GraphQL at this time. In addition to adding selector subscriptions, this PR modifies an error messages and does some minor, non-breaking type touchup. Closes #528
This non-breaking PR adds [`reselect`](https://npmjs.com/package/reselect)-style selectors as an optional parameter to controller messenger event subscriptions. We call subscriptions with selectors **selector subscriptions**. This enables subscribing to subsets of large event payloads, for example `*:stateChange` events. ```typescript // ControllerMessenger.subscribe selector signature subscribe<E extends Event['type'], V>( eventType: E, handler: (nextValue: V, previousValue: V | undefined) => void, selector: (payload: ExtractEventPayload<Event, E>) => V, ): void; ``` When a selector subscription is triggered, the corresponding selector is passed the entire event payload, and returns a single value. If the value has changed since the last occurrence of the event, the handler is called with the new value and the previous value, or `undefined` if no previous value exists. Since controller state is immutable, the event handler can efficiently diff the selector return values and trigger other logic dependent on changes to subsets of complex event payloads (e.g., other events). This feature is motivated by our frequent need to subscribe to controller substate(s). Every controller has a generic `*:stateChange` event, but parsing that state is often error-prone and/or annoying. By adding selector subscriptions, we can performantly parse complex controller state in one place, and emit more specific events as necessary. This implementation was chosen due to our existing widespread use of selectors in our UIs, and in order to avoid the introduction of querying DSLs such as JSONPath and GraphQL at this time. In addition to adding selector subscriptions, this PR modifies an error messages and does some minor, non-breaking type touchup. Closes #528
Closes #528
This non-breaking PR adds
reselect
-style selectors as an optional parameter to controller messenger event subscriptions. We call subscriptions with selectors selector subscriptions. This enables subscribing to subsets of large event payloads, for example*:stateChange
events.When a selector subscription is triggered, the corresponding selector is passed the entire event payload, and returns a single value. If the value has changed since the last occurrence of the event, the handler is called with the new value and the previous value, or
undefined
if no previous value exists. Since controller state is immutable, the event handler can efficiently diff the selector return values and trigger other logic dependent on changes to subsets of complex event payloads (e.g., other events).This feature is motivated by our frequent need to subscribe to controller substate(s). Every controller has a generic
*:stateChange
event, but parsing that state is often error-prone and/or annoying. By adding selector subscriptions, we can performantly parse complex controller state in one place, and emit more specific events as necessary. This implementation was chosen due to our existing widespread use of selectors in our UIs, and in order to avoid the introduction of querying DSLs such as JSONPath and GraphQL at this time.In addition to adding selector subscriptions, this PR modifies an error messages and does some minor, non-breaking type touchup.