Skip to content

Commit 0214cff

Browse files
committed
Refactor pubsub
1 parent fda64a9 commit 0214cff

File tree

5 files changed

+166
-398
lines changed

5 files changed

+166
-398
lines changed

@types/services/pubsub/pubsub.d.ts

Lines changed: 42 additions & 120 deletions
Original file line numberDiff line numberDiff line change
@@ -23,144 +23,66 @@ export class PubSubProvider {
2323
* - Minimal memory churn & stable hidden-class shapes
2424
* - Fast publish using flat arrays
2525
* - Preserves listener order
26+
* - All publishes are asynchronous (via queueMicrotask)
2627
*/
2728
export class PubSub {
28-
static $nonscope: boolean;
29-
disposed: boolean;
29+
/** @private {Object<string, Array<{fn: Function, context: any}>>} */
30+
private _topics;
31+
/** @private */
32+
private _disposed;
3033
/**
31-
* The next available subscription key. Internally, this is an index into the
32-
* sparse array of subscriptions.
33-
*
34-
* @private {number}
34+
* Set instance to initial state
3535
*/
36-
private key;
36+
reset(): void;
3737
/**
38-
* Array of subscription keys pending removal once publishing is done.
39-
*
40-
* @private {!Array<number>}
41-
* @const
38+
* Checks if instance has been disposed.
39+
* @returns {boolean} True if disposed.
4240
*/
43-
private pendingKeys;
44-
/**
45-
* Lock to prevent the removal of subscriptions during publishing. Incremented
46-
* at the beginning of {@link #publish}, and decremented at the end.
47-
*
48-
* @private {number}
49-
*/
50-
private publishDepth;
51-
/**
52-
* Sparse array of subscriptions. Each subscription is represented by a tuple
53-
* comprising a topic identifier, a function, and an optional context object.
54-
* Each tuple occupies three consecutive positions in the array, with the
55-
* topic identifier at index n, the function at index (n + 1), the context
56-
* object at index (n + 2), the next topic at index (n + 3), etc. (This
57-
* representation minimizes the number of object allocations and has been
58-
* shown to be faster than an array of objects with three key-value pairs or
59-
* three parallel arrays, especially on IE.)
60-
*
61-
* Once a subscription is removed via {@link unsubscribe} or {@link unsubscribeByKey}, the three
62-
* corresponding array elements are deleted, and never reused. This means the
63-
* total number of subscriptions during the lifetime of the pubsub channel is
64-
* limited by the maximum length of a JavaScript array to (2^32 - 1) / 3 =
65-
* 1,431,655,765 subscriptions, which should suffice for most applications.
66-
*
67-
* @private {!Array<?>}
68-
* @const
69-
*/
70-
private subscriptions;
71-
/**
72-
* Map of topics to arrays of subscription keys.
73-
*
74-
* @private {!Object<!Array<number>>}
75-
*/
76-
private topics;
77-
/**
78-
* Subscribes a function to a topic. The function is invoked as a method on
79-
* the given `opt_context` object, or in the global scope if no context
80-
* is specified. Subscribing the same function to the same topic multiple
81-
* times will result in multiple function invocations while publishing.
82-
* Returns a subscription key that can be used to unsubscribe the function from
83-
* the topic via {@link unsubscribeByKey}.
84-
*
85-
* @param {string} topic Topic to subscribe to.
86-
* @param {Function} fn Function to be invoked when a message is published to
87-
* the given topic.
88-
* @param {Object=} opt_context Object in whose context the function is to be
89-
* called (the global scope if none).
90-
* @return {number} Subscription key.
91-
*/
92-
subscribe(topic: string, fn: Function, opt_context?: any | undefined): number;
41+
isDisposed(): boolean;
9342
/**
94-
* Subscribes a single-use function to a topic. The function is invoked as a
95-
* method on the given `opt_context` object, or in the global scope if
96-
* no context is specified, and is then unsubscribed. Returns a subscription
97-
* key that can be used to unsubscribe the function from the topic via
98-
* {@link unsubscribeByKey}.
99-
*
100-
* @param {string} topic Topic to subscribe to.
101-
* @param {Function} fn Function to be invoked once and then unsubscribed when
102-
* a message is published to the given topic.
103-
* @param {Object=} opt_context Object in whose context the function is to be
104-
* called (the global scope if none).
105-
* @return {number} Subscription key.
43+
* Dispose the instance, removing all topics and listeners.
10644
*/
107-
subscribeOnce(
108-
topic: string,
109-
fn: Function,
110-
opt_context?: any | undefined,
111-
): number;
45+
dispose(): void;
11246
/**
113-
* Unsubscribes a function from a topic. Only deletes the first match found.
114-
* Returns a Boolean indicating whether a subscription was removed.
115-
*
116-
* @param {string} topic Topic to unsubscribe from.
117-
* @param {Function} fn Function to unsubscribe.
118-
* @param {Object=} opt_context Object in whose context the function was to be
119-
* called (the global scope if none).
120-
* @return {boolean} Whether a matching subscription was removed.
47+
* Subscribe a function to a topic.
48+
* @param {string} topic - The topic to subscribe to.
49+
* @param {Function} fn - The callback function to invoke when published.
50+
* @param {*} [context] - Optional `this` context for the callback.
51+
* @returns {() => boolean} A function that unsubscribes this listener.
12152
*/
122-
unsubscribe(
123-
topic: string,
124-
fn: Function,
125-
opt_context?: any | undefined,
126-
): boolean;
53+
subscribe(topic: string, fn: Function, context?: any): () => boolean;
12754
/**
128-
* Removes a subscription based on the key returned by {@link subscribe}.
129-
* No-op if no matching subscription is found. Returns a Boolean indicating
130-
* whether a subscription was removed.
131-
*
132-
* @param {number} key Subscription key.
133-
* @return {boolean} Whether a matching subscription was removed.
55+
* Subscribe a function to a topic only once.
56+
* Listener is removed before the first invocation.
57+
* @param {string} topic - The topic to subscribe to.
58+
* @param {Function} fn - The callback function.
59+
* @param {*} [context] - Optional `this` context for the callback.
60+
* @returns {() => boolean} A function that unsubscribes this listener.
13461
*/
135-
unsubscribeByKey(key: number): boolean;
62+
subscribeOnce(topic: string, fn: Function, context?: any): () => boolean;
13663
/**
137-
* Publishes a message to a topic. Calls functions subscribed to the topic in
138-
* the order in which they were added, passing all arguments along.
139-
*
140-
* If this object was created with async=true, subscribed functions are called
141-
* via `queueMicrotask`. Otherwise, the functions are called directly, and if
142-
* any of them throw an uncaught error, publishing is aborted.
143-
*
144-
* @param {string} topic Topic to publish to.
145-
* @param {...*} var_args Arguments that are applied to each subscription
146-
* function.
147-
* @return {boolean} Whether any subscriptions were called.
64+
* Unsubscribe a specific function from a topic.
65+
* Matches by function reference and optional context.
66+
* @param {string} topic - The topic to unsubscribe from.
67+
* @param {Function} fn - The listener function.
68+
* @param {*} [context] - Optional `this` context.
69+
* @returns {boolean} True if the listener was found and removed.
14870
*/
149-
publish(topic: string, ...var_args: any[]): boolean;
71+
unsubscribe(topic: string, fn: Function, context?: any): boolean;
15072
/**
151-
* Clears the subscription list for a topic, or all topics if unspecified.
152-
* @param {string=} opt_topic Topic to clear (all topics if unspecified).
73+
* Get the number of subscribers for a topic.
74+
* @param {string} topic
75+
* @returns {number}
15376
*/
154-
clear(opt_topic?: string | undefined): void;
77+
getCount(topic: string): number;
15578
/**
156-
* Returns the number of subscriptions to the given topic (or all topics if
157-
* unspecified). This number will not change while publishing any messages.
158-
* @param {string=} opt_topic The topic (all topics if unspecified).
159-
* @return {number} Number of subscriptions to the topic.
79+
* Publish a value to a topic asynchronously.
80+
* All listeners are invoked in the order they were added.
81+
* @param {string} topic - The topic to publish.
82+
* @param {...*} args - Arguments to pass to listeners.
83+
* @returns {boolean} True if any listeners exist for this topic.
16084
*/
161-
getCount(opt_topic?: string | undefined): number;
162-
isDisposed(): boolean;
163-
dispose(): void;
85+
publish(topic: string, ...args: any[]): boolean;
16486
}
16587
export const EventBus: PubSub;
16688
/**

src/directive/channel/channel.js

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ export function ngChannelDirective($eventBus) {
1313

1414
const hasTemplateContent = element.childNodes.length > 0;
1515

16-
const key = $eventBus.subscribe(channel, (value) => {
16+
const unsubscribe = $eventBus.subscribe(channel, (value) => {
1717
if (hasTemplateContent) {
1818
if (isObject(value)) {
1919
scope.$merge(value);
@@ -23,9 +23,7 @@ export function ngChannelDirective($eventBus) {
2323
}
2424
});
2525

26-
scope.$on("$destroy", () => {
27-
$eventBus.unsubscribeByKey(key);
28-
});
26+
scope.$on("$destroy", () => unsubscribe());
2927
},
3028
};
3129
}

src/directive/channel/channel.spec.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@ describe("channel", () => {
1818
});
1919

2020
spyOn(EventBus, "subscribe").and.callThrough();
21-
unsubscribeSpy = spyOn(EventBus, "unsubscribeByKey").and.callThrough();
2221
});
2322

2423
it("should subscribe to the specified EventBus channel", () => {
@@ -42,11 +41,12 @@ describe("channel", () => {
4241
});
4342

4443
it("should unsubscribe from the EventBus when the scope is destroyed", () => {
44+
EventBus.reset();
4545
element = $compile('<div ng-channel="testChannel"></div>')($scope);
46-
46+
expect(EventBus.getCount("testChannel")).toEqual(1);
4747
$scope.$destroy();
4848

49-
expect(unsubscribeSpy).toHaveBeenCalled();
49+
expect(EventBus.getCount("testChannel")).toEqual(0);
5050
});
5151

5252
it("should handle templates when EventBus emits a value", async () => {

0 commit comments

Comments
 (0)