@@ -16,15 +16,133 @@ See the License for the specific language governing permissions and
16
16
limitations under the License.
17
17
*/
18
18
19
- import { Dispatcher } from "flux" ;
20
-
21
19
import { Action } from "./actions" ;
22
20
import { ActionPayload , AsyncActionPayload } from "./payloads" ;
23
21
22
+ type DispatchToken = string ;
23
+
24
+ function invariant ( cond : any , error : string ) : void {
25
+ if ( ! cond ) throw new Error ( error ) ;
26
+ }
27
+
24
28
/**
25
29
* A dispatcher for ActionPayloads (the default within the SDK).
30
+ * Based on the old Flux dispatcher https://github.com/facebook/flux/blob/main/src/Dispatcher.js
26
31
*/
27
- export class MatrixDispatcher extends Dispatcher < ActionPayload > {
32
+ export class MatrixDispatcher {
33
+ private readonly callbacks = new Map < DispatchToken , ( payload : ActionPayload ) => void > ( ) ;
34
+ private readonly isHandled = new Map < DispatchToken , boolean > ( ) ;
35
+ private readonly isPending = new Map < DispatchToken , boolean > ( ) ;
36
+ private pendingPayload ?: ActionPayload ;
37
+ private lastId = 1 ;
38
+
39
+ /**
40
+ * Registers a callback to be invoked with every dispatched payload. Returns
41
+ * a token that can be used with `waitFor()`.
42
+ */
43
+ public register ( callback : ( payload : ActionPayload ) => void ) : DispatchToken {
44
+ const id = "ID_" + this . lastId ++ ;
45
+ this . callbacks . set ( id , callback ) ;
46
+ if ( this . isDispatching ( ) ) {
47
+ // If there is a dispatch happening right now then the newly registered callback should be skipped
48
+ this . isPending . set ( id , true ) ;
49
+ this . isHandled . set ( id , true ) ;
50
+ }
51
+ return id ;
52
+ }
53
+
54
+ /**
55
+ * Removes a callback based on its token.
56
+ */
57
+ public unregister ( id : DispatchToken ) : void {
58
+ invariant ( this . callbacks . has ( id ) , `Dispatcher.unregister(...): '${ id } ' does not map to a registered callback.` ) ;
59
+ this . callbacks . delete ( id ) ;
60
+ }
61
+
62
+ /**
63
+ * Waits for the callbacks specified to be invoked before continuing execution
64
+ * of the current callback. This method should only be used by a callback in
65
+ * response to a dispatched payload.
66
+ */
67
+ public waitFor ( ids : DispatchToken [ ] ) : void {
68
+ invariant ( this . isDispatching ( ) , "Dispatcher.waitFor(...): Must be invoked while dispatching." ) ;
69
+ for ( const id of ids ) {
70
+ if ( this . isPending . get ( id ) ) {
71
+ invariant (
72
+ this . isHandled . get ( id ) ,
73
+ `Dispatcher.waitFor(...): Circular dependency detected while waiting for '${ id } '.` ,
74
+ ) ;
75
+ continue ;
76
+ }
77
+ invariant (
78
+ this . callbacks . get ( id ) ,
79
+ `Dispatcher.waitFor(...): '${ id } ' does not map to a registered callback.` ,
80
+ ) ;
81
+ this . invokeCallback ( id ) ;
82
+ }
83
+ }
84
+
85
+ /**
86
+ * Dispatches a payload to all registered callbacks.
87
+ */
88
+ // eslint-disable-next-line @typescript-eslint/naming-convention
89
+ private _dispatch = ( payload : ActionPayload ) : void => {
90
+ invariant ( ! this . isDispatching ( ) , "Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch." ) ;
91
+ this . startDispatching ( payload ) ;
92
+ try {
93
+ for ( const [ id ] of this . callbacks ) {
94
+ if ( this . isPending . get ( id ) ) {
95
+ continue ;
96
+ }
97
+ this . invokeCallback ( id ) ;
98
+ }
99
+ } finally {
100
+ this . stopDispatching ( ) ;
101
+ }
102
+ } ;
103
+
104
+ /**
105
+ * Is this Dispatcher currently dispatching.
106
+ */
107
+ public isDispatching ( ) : boolean {
108
+ return ! ! this . pendingPayload ;
109
+ }
110
+
111
+ /**
112
+ * Call the callback stored with the given id. Also do some internal
113
+ * bookkeeping.
114
+ *
115
+ * Must only be called with an id which has a callback and pendingPayload set
116
+ * @internal
117
+ */
118
+ private invokeCallback ( id : DispatchToken ) : void {
119
+ this . isPending . set ( id , true ) ;
120
+ this . callbacks . get ( id ) ! ( this . pendingPayload ! ) ;
121
+ this . isHandled . set ( id , true ) ;
122
+ }
123
+
124
+ /**
125
+ * Set up bookkeeping needed when dispatching.
126
+ *
127
+ * @internal
128
+ */
129
+ private startDispatching ( payload : ActionPayload ) : void {
130
+ for ( const [ id ] of this . callbacks ) {
131
+ this . isPending . set ( id , false ) ;
132
+ this . isHandled . set ( id , false ) ;
133
+ }
134
+ this . pendingPayload = payload ;
135
+ }
136
+
137
+ /**
138
+ * Clear bookkeeping used for dispatching.
139
+ *
140
+ * @internal
141
+ */
142
+ private stopDispatching ( ) : void {
143
+ this . pendingPayload = undefined ;
144
+ }
145
+
28
146
/**
29
147
* Dispatches an event on the dispatcher's event bus.
30
148
* @param {ActionPayload } payload Required. The payload to dispatch.
@@ -42,14 +160,14 @@ export class MatrixDispatcher extends Dispatcher<ActionPayload> {
42
160
}
43
161
44
162
if ( sync ) {
45
- super . dispatch ( payload ) ;
163
+ this . _dispatch ( payload ) ;
46
164
} else {
47
165
// Unless the caller explicitly asked for us to dispatch synchronously,
48
166
// we always set a timeout to do this: The flux dispatcher complains
49
167
// if you dispatch from within a dispatch, so rather than action
50
168
// handlers having to worry about not calling anything that might
51
169
// then dispatch, we just do dispatches asynchronously.
52
- window . setTimeout ( super . dispatch . bind ( this , payload ) , 0 ) ;
170
+ window . setTimeout ( this . _dispatch , 0 , payload ) ;
53
171
}
54
172
}
55
173
0 commit comments