Search code examples
reactjsreact-reduxmobxmobx-state-tree

Is there any way to cancel previous async action in mobx-state-tree (Similar to takeLatest in redux-saga)


Currently, I am working on my react-native application using MST as a state management library.

Now I have encountered an issue where the app has a chance to fire 2 similar API calls. The 1st API responded after the 2nd one, which caused the data to be overridden by an outdated response.

In redux-saga, we can use takeLatest to make sure we get the data from the latest request. I am looking for similar function in MST to address the problem.

I have found that there's Axios' cancel token to cancel the API calls, but I want to see is there any way in a more generic async way to solve it.


Solution

  • As far as I know there is no build in features like that in MobX or MST, so you would need to implement it by yourself.

    Generic way I usually use to cancel promises is this one (credit to https://wanago.io):

    class RaceConditionGuard {
      private lastPromise: PromiseLike<unknown> | null = null;
    
      getGuardedPromise<T>(promise: PromiseLike<T>) {
        this.lastPromise = promise;
        return this.lastPromise.then(this.preventRaceCondition()) as Promise<T>;
      }
    
      preventRaceCondition() {
        const currentPromise = this.lastPromise;
        return (response: unknown) => {
          if (this.lastPromise !== currentPromise) {
            return new Promise(() => null);
          }
          return response;
        };
      }
    
      cancel = () => {
        this.lastPromise = null;
      };
    }
    

    And the usage, assuming you have some class based store, for example:

    class SomeStore {
      raceConditionGuard = new RaceConditionGuard();
    
      loadItems = () => {
        // Previous call will be automatically canceled (it will never resolve actually)
        this.raceConditionGuard
          // Wrap your async operation
          .getGuardedPromise(fetchSomething())
          // Handle result somehow
          .then(this.handleResult);
      };
    
      // Or you can cancel manually
      cancelLoading = () => {
        this.raceConditionGuard.cancel()
      }
    
      // ...
    }