Vuex Store: Async Operations on Stale State

444 Views Asked by At

My Vue application uses a Vuex store. Sometimes the user can "reset" the session. This entails resetting the vuex store to the default values and so that ANY pending async operations should be dropped because the Vuex store has been reset. The issue is that when the async calls return, they attempt to modify the store but it has since been reset. How can I force ANY pending async operation to be ignored when the user hits "reset".

I thought of recalling the "createStore" Vuex function but this lead to a dead-end because the global store variable is updated.

Edit: I end up passing a StoreID with EVERY mutation. I store this StoreID value in a variable at the beginning of all my async ACTIONS. The mutation verifies when it's called that the store currently is at the same StoreID version. Is there a more clever way to do this?

2

There are 2 best solutions below

0
On

You could move the StoreID check to the request layer before you resolve / reject the request promise. If the StoreID has changed since the request was made, ignore the result by not calling resolve or reject. This will allow you to remove the check from the rest of your store as the mutations will never be called for requests sent before you updated the StoreID.

Another option if you are using axios or something that has the ability to cancel requests is to save all pending request cancellation tokens in an array and cancel them all when you are resetting your store. A gotcha here is that you will need to check in your request error handling if the reason your request was rejected is because it was canceled.

axios.isCancel(err); // will be true if request was canceled
0
On

Case 1. you are using fetch:

const controller = new AbortController();
const {
  signal
} = controller;

actions: {
  fetchSoemthing(context, { url, options }) {
    fetch(url, {
      ...options,
      signal, //<------ This is our AbortSignal
    }).then(response => {
      console.log(`Request 1 is complete!`);
    }).catch(e => {
      console.warn(`Fetch 1 error: ${e.message}`);
    });
  },
  cancelAllRequests(context) {
    return controller.abort(); // in your usecase when you call the resetStoreData, you also dispatch this action.
  }
}

I am not 100% sure on how the signal works, but my best advice would be for you to use a wrapper class for fetch not to use it like this directly in the store, but try it out with your current implementation and see how it goes.

Case 2, you are using axios:

import axios from "axios";

const { CancelToken } = axios;
const cancelToken = CancelToken.source();

actions: {
  fetchSoemthing(context, {
    url,
    options
  }) {
    axios.post(url, options, {
        cancelToken,
      })
      .then((response) => {
        //responce Body
      })
      .catch((error) => {
        if (axios.isCancel(error)) {
          console.log("post Request canceled");
        }
      });
  },
  cancelAllRequests(context) {
    return cancelToken.cancel(); // in your usecase when you call the resetStoreData, you also dispatch this action.
  }
}

For both implementations you have to use the same canceltoken / signal for every request if you want to cancel them at the same time so I would recommend that you declare this either in an external file, or in Vuex state.