Aurelia Store group/cancel dispatches with same arguments

Hi,

i have a lot of components to display different parts of a entity stored in the store. You can bind the entity id to the component and in the attached method, the component checks if the entity is already in the store and if not, dispatches the load action which in turn creates an api call to load the entity. The promise of the dispatch call is awaited to show an loading animation.

The problem is now, when i have multiple components on the same page and the entity was not loaded before, each component dispatches the load action and the api gets called multiple times.

Do you know a way, to access the currently queued/running actions with parameters, to return the promise of the running action instead of enqueuing a new action when calling dispatch?

1 Like

Hey there,

what you’re trying sounds super complicated. How about instead you don’t directly dispatch the call from every component but instead move that part to a Service which gets injected to the components and has a method to do it. In there you could simply await the call and block others if there’s already one ongoing.

EDIT:
the dispatchqueue itself is a private field on the store called dispatchQueue. It’s private since its not really something you’d need on a regular basis, but in worst case you can cast your store to any and access the private anyway and just lookup whats in there. The queue itself holds a DispatchQueueItem which again contains the action as DispatchAction, which in turn has the reducer plus params available. See here for the interfaces.

2 Likes

@zewa666 thank you!

I created a Service that dispatches actions and returns the promise of the running action, if the action with the same parameters is already running.

Maybe others find my solution helpful, so here it is:

@autoinject()
export class SingletonDispatcher {
    private readonly _runningActions: Record<string, Promise<void>> = {};

    constructor(private readonly _store: Store<State>) {}

    public async Dispatch<P extends any[]>(
        reducer: Reducer<State, P> | string,
        ...params: P
    ): Promise<void> {
        const actionString = `${
            typeof reducer === "string" ? reducer : reducer.name
        } | ${JSON.stringify(params)}`;

        if (actionString in this._runningActions) {
            return this._runningActions[actionString];
        }
        const promise = this._store.dispatch(reducer, ...params);
        this._runningActions[actionString] = promise;
        try {
            await promise;
        } catch(e) {
            // Log error
        } finally {
            delete this._runningActions[actionString];
        }
        return promise;
    }
}
4 Likes

Very well done. The var promise could also be const vs let and it would make sense to add a try catch for the await and delete inside finally

2 Likes

Thanks!

I updated my solution with your suggestions :+1:

2 Likes