vuejs / vuex

🗃️ Centralized State Management for Vue.js.
https://vuex.vuejs.org
MIT License
28.42k stars 9.58k forks source link

dispatch: Enable extending built-in Promise by not wrapping action Promise #1856

Open Jokab opened 4 years ago

Jokab commented 4 years ago

What problem does this feature solve?

At the moment it does not seem to be possible for an action to return a custom promise class, due to dispatch creating its own Promise, using the built-in Promise.

This fact makes it seemingly impossible to fully use Vuex in use cases where it's necessary to cancel an ongoing promise. This would be useful for example when the action initiates a promise that contains an setInterval which needs to be cleared when the user performs some action. Leaving the promise to continue polling would be unnecessarily expensive.

To illustrate, below follows an example. I want the action to return my custom CancellablePromise in which I have extended the promise executor with an onCancel delegate which is called when promise.cancel is called.

Action

async [SomeAction.foo]({ state, dispatch, commit, rootGetters }) {
  const cancellablePromise = new CancellablePromise<any>((resolve, reject, onCancel) => {
    const interval = setInterval(async () => {
      const status = await dispatch(SomeAction.bar);
      if (status === "goodstatus") {
        clearInterval(interval);
        resolve();
      } else if (status === "badstatus") {
        clearInterval(interval);
        reject();
      } else if (status === "pending") {
        ; // continue polling
      }
    }, 2000);

    onCancel(() => {
      clearInterval(interval);
      reject();
    });
  });

  return cancellablePromise;
}

Component

data: (() => {
  promise: undefined as CancellablePromise<any> | undefined
}),

async call() {
  this.promise = this.$store
    .dispatch(SomeAction.foo)
    .then(response => {
      // do something
    }) as CancellablePromise<any>;
},

userTookAction(event): void {
  if (this.promise) {
    this.promise.cancel(); // outputs cancel is not a function
  }
}

As specified, this.promise.cancel() outputs that cancel is not a function, because my CancellablePromise is wrapped by dispatch.

To work around this problem I have been forced to create my own "actions" in a separate module which I call directly instead of through dispatch, and to which I pass the ActionContext in order to use the rest of Vuex.

What does the proposed API look like?

I am not familiar with the internal workings of Vuex, but I would simply want the above example to work. Ideally this.$store.dispatch(action) would return the same type as returned by the action itself.

kiaking commented 4 years ago

Thanks for the feedback! Interesting one. Maybe as you mentioned we should just return the exact same promise that is being returned from actions... though not sure what kind of impact we would have in terms of breaking changes. Need to look into it.

Jokab commented 4 years ago

Thanks for responding! It would be greatly appreciated, though I understand it won't happen in the near future.

kiaking commented 4 years ago

Yeah, it would be helpful if anyone can look into this issue and check for the impact. If we can believe it's safe, I don't have any reason to reject the proposal 👍