Skip to content
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

Merged
merged 7 commits into from
Aug 5, 2021

Conversation

rekmarks
Copy link
Member

@rekmarks rekmarks commented Aug 3, 2021

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.

// 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.

@rekmarks rekmarks requested a review from Gudahtt August 3, 2021 17:55
@rekmarks rekmarks requested a review from a team as a code owner August 3, 2021 17:55
@rekmarks rekmarks changed the title Add optional selector parameter to controller messenger subscriptions Feature: Add selector subscriptions Aug 3, 2021
Comment on lines 409 to 463
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);
}
Copy link
Member Author

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

Copy link
Member Author

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

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ref: #566

@rekmarks rekmarks marked this pull request as draft August 4, 2021 16:00
@rekmarks rekmarks force-pushed the controller-messenger-selector-subscriptions branch from e44ebcb to ee67ca9 Compare August 4, 2021 16:13
@rekmarks rekmarks marked this pull request as ready for review August 4, 2021 16:14
Copy link
Member

@Gudahtt Gudahtt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@rekmarks rekmarks merged commit a143120 into main Aug 5, 2021
@rekmarks rekmarks deleted the controller-messenger-selector-subscriptions branch August 5, 2021 19:32
rickycodes added a commit that referenced this pull request Aug 10, 2021
…to dynamic-speed-up

* 'dynamic-speed-up' of github.com:MetaMask/controllers:
  Consolidate token list controller data (#527)
  14.1.0 (#553)
  Feature: Add selector subscriptions (#551)
MajorLift pushed a commit that referenced this pull request Oct 11, 2023
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
MajorLift pushed a commit that referenced this pull request Oct 11, 2023
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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add BaseControllerV2 substate subscription mechanism
2 participants