VueJs + Vuex + mapActions

vuejs2vuex

In the documentation, it is written that the state is immutable apart from the mutations called via the actions … Ok.

I use in my component, mapGetters, mapActions …

store :

export default {
  namespaced: true,

  state: {
    color: "violet"
  },
  mutations: {
      changeColor(state, newColor) {
          state.color = newColor
      },
  },
  actions: {
    changeColor({ commit }, newColor) {
      commit('changeColor', newColor)
  }
 }

component :

...
methods: {
    ...mapActions({
      setColor: 'store/changeColor',
    }),
    myMethodCallByButton(){
       this.setColor("blue").then(response => {
          console.log("change Color done")
       },err => {
          console.log("change Color error")
       })
    }
...

The method works fine, the store is updated, EXCEPT that I never receive the console.log ().

It is written in the documentation that mapActions were equivalent to this.$store.dispatch.

  • Why do not I get the message?
  • Is there another solution ?

PS: I want to keep the mapGetters map, mapActions .. I do not like calling this.$store.dispatch

PS2: I work with modules in my store

Thank you

Best Answer

Every Vuex action returns a Promise.

Vuex wraps the results of the action functions into Promises. So the changeColor action in:

actions: {
  changeColor({ commit }, newColor) {
    myAsyncCommand();
  }
}

Returns a Promise that resolves to undefined and that will not wait for the completion myAsyncCommand();'s asynchronous code (if it doesn't contain async code, then there's no waiting to do).

This happens because the code above is the same as:

  changeColor({ commit }, newColor) {
    myAsyncCommand();
    return undefined;
  }

And when .dispatch('changeColor', ...) Vuex will then return Promise.resolve(undefined).

If you want the Promise returned by the action to wait, you should return a Promise that does the propert waiting yourself. Something along the lines of:

  changeColor({ commit }, newColor) {
    return new Promise((resolve, reject) => {
      myAsyncCommand().then(resolve);
    });
    // or, simply: return myAsyncCommand();
  }

Demo implementation below with more details:

const myStore = {
  namespaced: true,
  state: { color: "violet" },
  mutations: {
      changeColor(state, newColor) {
          state.color = newColor
      }
  },
  actions: {
    changeColor_SIMPLE({ commit }, newColor) {
      commit('changeColor', newColor)
    },
    changeColor_COMPLICATED_NO_PROMISE({ commit }, newColor) {
        setTimeout(() => {
          commit('changeColor', newColor)
        }, 2000)
    },
    changeColor_COMPLICATED_WITH_PROMISE({ commit }, newColor) {
      return new Promise((resolve, reject) => {
        setTimeout(() => {
          commit('changeColor', newColor)
          resolve();
        }, 2000)
      });
    }
  }
};
const store = new Vuex.Store({
  modules: {
    store: myStore,
  }
});
new Vue({
  store,
  el: '#app',
  methods: {
    ...Vuex.mapActions({
      setColorSimple: 'store/changeColor_SIMPLE',
      setColorComplicatedNoPromise: 'store/changeColor_COMPLICATED_NO_PROMISE',
      setColorComplicatedWithPromise: 'store/changeColor_COMPLICATED_WITH_PROMISE',
    }),
    myMethodCallByButton(){
       this.setColorSimple("blue")
       	.then(response => console.log("SIMPLE done"),err => console.log("SIMPLE err"));
      this.setColorComplicatedNoPromise("blue")
       	.then(response => console.log("NO_PROMISE done"),err => console.log("NO_PROMISE err"));
      this.setColorComplicatedWithPromise("blue")
       	.then(response => console.log("WITH_PROMISE done"),err => console.log("WITH_PROMISE err"));
    }
  }
})
<script src="https://unpkg.com/vue@2.5.16/dist/vue.min.js"></script>
<script src="https://unpkg.com/vuex"></script>

<div id="app">
  <p>color: {{ $store.state.store.color }}</p>
  <button @click="myMethodCallByButton">click me and WAIT for 2s</button>
</div>

Update/Per comments:

Even if the mapAction / dispatch returns a promised, I am in my case obliged to add a promise to wait for the end of the "mutation". I thought, from the documentation, that it was precisely managed via the mapAction. Is it exact?

If an action calls a mutation only, such as:

actions: {
  changeColor({ commit }, newColor) {
    commit('changeColor', newColor)
    return undefined; // added for clarity
  }
}

Then the returned Promise will only execute after the commit() completes.

That does not happen because Vuex manages waiting of mutations (commits).

It happens that way because there's no waiting to do. This is because Vuex requires: mutations must be synchronous operations.

Since mutations are synchronous, the line of the return above will only execute after the code of the line before (commit('changeColor', newColor)).

Note: If your mutations have asynchronous code, you should make them synchronous, as it goes against how Vuex properly works and may yield all kinds of unexpected behaviors.

Related Topic