Last active
March 11, 2024 19:38
-
-
Save arleighdickerson/6ea89063da981128bc99f27247273e69 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export interface StandardAction { | |
type: string; | |
payload?: any; | |
error?: any; | |
meta?: any; | |
} | |
export interface IDispatchStore { | |
handleDispatch: (action: StandardAction) => void; | |
dispatchToken: string; | |
dispatcher: IDispatcher; | |
} | |
export interface IDispatcher { | |
isDispatching: boolean; | |
register: (callback: (action: StandardAction) => void) => string; | |
unregister: (id: string) => void; | |
waitFor: (ids: string[]) => void; | |
dispatch: (action: StandardAction) => void; | |
} | |
// @Component | |
export class Dispatcher implements IDispatcher { | |
private static __prefix__ = 0; | |
private readonly idPrefix = `[${Dispatcher.__prefix__++}]`; | |
private readonly callbacks: { | |
[key: string]: (action: StandardAction) => void; | |
} = {}; | |
private readonly handled: { [key: string]: boolean } = {}; | |
private readonly pending: { [key: string]: boolean } = {}; | |
private lastId = 1; | |
private pendingAction?: StandardAction; | |
private _isDispatching = false; | |
get isDispatching(): boolean { | |
return this._isDispatching; | |
} | |
register(callback: (action: StandardAction) => void): string { | |
const id = this.idPrefix + this.lastId++; | |
this.callbacks[id] = callback; | |
return id; | |
} | |
unregister(id: string): void { | |
if (!this.callbacks[id]) { | |
throw new Error('Dispatcher.unregister(...): "+ id +" does not map to a registered callback.'); | |
} | |
delete this.callbacks[id]; | |
} | |
waitFor(ids: string[]): void { | |
if (!this._isDispatching) { | |
throw new Error('Dispatcher.waitFor(...): Must be invoked while dispatching.'); | |
} | |
for (let i = 0; i < ids.length; i++) { | |
const id = ids[i]; | |
if (this.pending[id]) { | |
if (!this.handled[id]) { | |
throw new Error(`Dispatcher.waitFor(...): Circular dependency detected while waiting for ${id}`); | |
} | |
continue; | |
} | |
if (!this.callbacks[id]) { | |
throw new Error(`Dispatcher.waitFor(...): ${id} does not map to a registered callback.`); | |
} | |
this.invokeCallback(id); | |
} | |
} | |
dispatch(action: StandardAction): void { | |
// logger.debug(`Dispatching: ${action.type}`); | |
if (this._isDispatching) { | |
throw new Error('Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch.'); | |
} | |
this.startDispatching(action); | |
try { | |
for (const id in this.callbacks) { | |
if (this.pending[id]) { | |
continue; | |
} | |
this.invokeCallback(id); | |
} | |
} finally { | |
this.stopDispatching(); | |
} | |
} | |
private invokeCallback(id: string): void { | |
this.pending[id] = true; | |
this.callbacks[id](this.pendingAction as StandardAction); | |
this.handled[id] = true; | |
} | |
private startDispatching(action: StandardAction): void { | |
Object.keys(this.callbacks).forEach(id => { | |
this.pending[id] = false; | |
this.handled[id] = false; | |
}); | |
this.pendingAction = action; | |
this._isDispatching = true; | |
} | |
private stopDispatching(): void { | |
delete this.pendingAction; | |
this._isDispatching = false; | |
} | |
} | |
export abstract class DispatchStore implements IDispatchStore { | |
private _dispatchToken?: string; | |
abstract readonly dispatcher: Dispatcher; | |
get dispatchToken(): string { | |
if (!this._dispatchToken) { | |
throw new Error('self._dispatchToken is not set'); | |
} | |
return this._dispatchToken; | |
} | |
abstract handleDispatch(action: StandardAction): void; | |
// @PostConstruct | |
protected registerWithDispatcher() { | |
this._dispatchToken = this.dispatcher.register(this.handleDispatch); | |
} | |
// @PreDestroy | |
protected unregisterWithDispatcher() { | |
this.dispatcher.unregister(this.dispatchToken); | |
} | |
} | |
type ThunkAction = (dispatch: IDispatcher['dispatch']) => Promise<void>; | |
export function createThunkDispatch(dispatcher: IDispatcher) { | |
const dispatch = async (action: ThunkAction | StandardAction) => { | |
if (action instanceof Function) { | |
await action(dispatch); | |
} else { | |
dispatcher.dispatch(action); | |
} | |
}; | |
return dispatch; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment