diff --git a/packages/context/src/__tests__/unit/context-observer.unit.ts b/packages/context/src/__tests__/unit/context-observer.unit.ts index 9f757b8ede59..20fc4b6ed037 100644 --- a/packages/context/src/__tests__/unit/context-observer.unit.ts +++ b/packages/context/src/__tests__/unit/context-observer.unit.ts @@ -148,8 +148,8 @@ describe('Context', () => { 'SYNC:foo:foo-value:bind', 'ASYNC:foo:foo-value:bind', 'SYNC:foo:foo-value:unbind', - 'SYNC:foo:new-foo-value:bind', 'ASYNC:foo:foo-value:unbind', + 'SYNC:foo:new-foo-value:bind', 'ASYNC:foo:new-foo-value:bind', ]); expect(nonMatchingObserverCalled).to.be.false(); diff --git a/packages/context/src/context-observer.ts b/packages/context/src/context-observer.ts index c1adbcf6b9f3..38145f8628b5 100644 --- a/packages/context/src/context-observer.ts +++ b/packages/context/src/context-observer.ts @@ -63,3 +63,25 @@ export interface Subscription { */ closed: boolean; } + +/** + * Event data for observer notifications + */ +export type Notification = { + /** + * Context event type - bind/unbind + */ + eventType: ContextEventType; + /** + * Binding added/removed + */ + binding: Readonly>; + /** + * Owner context for the binding + */ + context: Context; + /** + * A snapshot of observers when the original event is emitted + */ + observers: Set; +}; diff --git a/packages/context/src/context.ts b/packages/context/src/context.ts index 1d3b8b6d887b..4b033f5c82ca 100644 --- a/packages/context/src/context.ts +++ b/packages/context/src/context.ts @@ -14,6 +14,7 @@ import { ContextEventObserver, ContextEventType, ContextObserver, + Notification, Subscription, } from './context-observer'; import {ResolutionOptions, ResolutionSession} from './resolution-session'; @@ -152,8 +153,7 @@ export class Context extends EventEmitter { // The following are two async functions. Returned promises are ignored as // they are long-running background tasks. - this.startNotificationTask('bind').catch(notificationErrorHandler); - this.startNotificationTask('unbind').catch(notificationErrorHandler); + this.startNotificationTask().catch(notificationErrorHandler); } /** @@ -197,32 +197,14 @@ export class Context extends EventEmitter { /** * Start a background task to listen on context events and notify observers - * @param eventType Context event type - */ - private async startNotificationTask(eventType: ContextEventType) { - const notificationEvent = `${eventType}-notification`; - this.on(eventType, (binding, context) => { - // No need to schedule notifications if no observers are present - if (this.observers.size === 0) return; - // Track pending events - this.pendingEvents++; - // Take a snapshot of current observers to ensure notifications of this - // event will only be sent to current ones. Emit a new event to notify - // current context observers. - this.emit(notificationEvent, { - binding, - context, - observers: new Set(this.observers), - }); - }); - // FIXME(rfeng): p-event should allow multiple event types in an iterator. - // Create an async iterator from the given event type - const events: AsyncIterable<{ - binding: Readonly>; - context: Context; - observers: Set; - }> = pEvent.iterator(this, notificationEvent); - for await (const {binding, context, observers} of events) { + */ + private async startNotificationTask() { + this.setupNotification('bind', 'unbind'); + const events: AsyncIterable = pEvent.iterator( + this, + 'notification', + ); + for await (const {eventType, binding, context, observers} of events) { // The loop will happen asynchronously upon events try { // The execution of observers happen in the Promise micro-task queue @@ -244,6 +226,31 @@ export class Context extends EventEmitter { } } + /** + * Listen on given event types and emit `notification` event. This method + * merge multiple event types into one for notification. + * @param eventTypes Context event types + */ + private setupNotification(...eventTypes: ContextEventType[]) { + for (const eventType of eventTypes) { + this.on(eventType, (binding, context) => { + // No need to schedule notifications if no observers are present + if (this.observers.size === 0) return; + // Track pending events + this.pendingEvents++; + // Take a snapshot of current observers to ensure notifications of this + // event will only be sent to current ones. Emit a new event to notify + // current context observers. + this.emit('notification', { + eventType, + binding, + context, + observers: new Set(this.observers), + }); + }); + } + } + /** * Wait until observers are notified for all of currently pending events. *