173

I recently started migrating things from jQ to a more structured framework being VueJS, and I love it!

Conceptually, Vuex has been a bit of a paradigm shift for me, but I'm confident I know what its all about now, and totally get it! But there exist a few little grey areas, mostly from an implementation standpoint.

This one I feel is good by design, but don't know if it contradicts the Vuex cycle of uni-directional data flow.

Basically, is it considered good practice to return a promise(-like) object from an action? I treat these as async wrappers, with states of failure and the like, so seems like a good fit to return a promise. Contrarily mutators just change things, and are the pure structures within a store/module.

5 Answers 5

314

actions in Vuex are asynchronous. The only way to let the calling function (initiator of action) to know that an action is complete - is by returning a Promise and resolving it later.

Here is an example: myAction returns a Promise, makes a http call and resolves or rejects the Promise later - all asynchronously

actions: {
    myAction(context, data) {
        return new Promise((resolve, reject) => {
            // Do something here... lets say, a http call using vue-resource
            this.$http("/api/something").then(response => {
                // http success, call the mutator and change something in state
                resolve(response);  // Let the calling function know that http is done. You may send some data back
            }, error => {
                // http failed, let the calling function know that action did not work out
                reject(error);
            })
        })
    }
}

Now, when your Vue component initiates myAction, it will get this Promise object and can know whether it succeeded or not. Here is some sample code for the Vue component:

export default {
    mounted: function() {
        // This component just got created. Lets fetch some data here using an action
        this.$store.dispatch("myAction").then(response => {
            console.log("Got some data, now lets show something in this component")
        }, error => {
            console.error("Got nothing from server. Prompt user to check internet connection and try again")
        })
    }
}

As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface.

And a last note regarding mutators - as you rightly pointed out, they are synchronous. They change stuff in the state, and are usually called from actions. There is no need to mix Promises with mutators, as the actions handle that part.

Edit: My views on the Vuex cycle of uni-directional data flow:

If you access data like this.$store.state["your data key"] in your components, then the data flow is uni-directional.

The promise from action is only to let the component know that action is complete.

The component may either take data from promise resolve function in the above example (not uni-directional, therefore not recommended), or directly from $store.state["your data key"] which is unidirectional and follows the vuex data lifecycle.

The above paragraph assumes your mutator uses Vue.set(state, "your data key", http_data), once the http call is completed in your action.

20
  • 11
    "As you can see above, it is highly beneficial for actions to return a Promise. Otherwise there is no way for the action initiator to know what is happening and when things are stable enough to show something on the user interface." IMO, this is missing the point of Vuex. The action initiator shouldn't need to know what's happening. The action should mutate the state when data comes back from the asynchronous event, and the component should respond to that stage change based on the Vuex store's state, not a Promise.
    – ceejayoz
    Commented Oct 21, 2016 at 3:00
  • 8
    @ceejayoz Check out Composing Actions (last section) in the docs - vuex.vuejs.org/en/actions.html - actions are asynchronous and therefore returning a Promise is a good idea, as stated in those docs. Maybe not in $http case above, but in some other case we may need to know when an action is completed. Commented Oct 21, 2016 at 17:12
  • 6
    @ceejayoz is correct that your store can certainly be used to provide feedback on when actions have completed, and sometimes this is what you should do. i.e. fire an action and commit mutations on complete (pass or fail). Vuex is reactive so states can be watched very easily. However Mani's idea is also valid as it provides the ability to both chain Promises which allows for a far clearer workflow, and also commit mutations prior to completing as well. Therefore on completion of the Promise you know the state is correct as you have already called the synchronous mutations.
    – GuyC
    Commented Oct 25, 2016 at 7:46
  • 10
    @DanielPark Yes, "it depends" on the scenario and individual developer preferences. In my case, I wanted to avoid intermediate values like {isLoading:true} in my state, and therefore resorted to Promises. Your preferences may vary. At the end of the day, our objective is to write clutter-free and maintainable code. Whether promise achieves that objective, or vuex state - is left to individual developers and teams to decide. Commented Oct 31, 2016 at 13:45
  • 3
    @Mani good lord you were right, figured it out while making the fiddle. Many thanks! Commented Nov 15, 2017 at 21:21
57

Just for an information on a closed topic: you don’t have to create a promise, axios returns one itself:

Ref: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4

Example:

    export const loginForm = ({ commit }, data) => {
      return axios
        .post('http://localhost:8000/api/login', data)
        .then((response) => {
          commit('logUserIn', response.data);
        })
        .catch((error) => {
          commit('unAuthorisedUser', { error:error.response.data });
        })
    }

Another example:

    addEmployee({ commit, state }) {       
      return insertEmployee(state.employee)
        .then(result => {
          commit('setEmployee', result.data);
          return result.data; // resolve 
        })
        .catch(err => {           
          throw err.response.data; // reject
        })
    }

Another example with async-await

    async getUser({ commit }) {
        try {
            const currentUser = await axios.get('/user/current')
            commit('setUser', currentUser)
            return currentUser
        } catch (err) {
            commit('setUser', null)
            throw 'Unable to fetch current user'
        }
    },
2
  • 1
    Should the last example not be redundant as axios actions are per default already asynchronous? Commented Jun 9, 2020 at 21:40
  • The last example is for a case where you have a custom operation to be done before you want to return the return-object. It is not returning a promise in any case. Commented Aug 29, 2022 at 15:19
9

Actions

ADD_PRODUCT : (context,product) => {
  return Axios.post(uri, product).then((response) => {
    if (response.status === 'success') {  
      context.commit('SET_PRODUCT',response.data.data)
    }
    return response.data
  });
});

Component

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})
4
  • 3
    this not working response undefined in the component
    – Nand Lal
    Commented Aug 23, 2019 at 15:19
  • 2
    I think you forgot to add return in ADD_PRODUCT function Commented Aug 26, 2019 at 2:45
  • Should be lowercase “a” in “axios”. Commented Nov 2, 2019 at 14:21
  • I taken Axois as const that is importing from 'axios' Commented Nov 7, 2019 at 2:42
1

TL:DR; return promises from you actions only when necessary, but DRY chaining the same actions.

For a long time I also though that returning actions contradicts the Vuex cycle of uni-directional data flow.

But, there are EDGE CASES where returning a promise from your actions might be "necessary".

Imagine a situation where an action can be triggered from 2 different components, and each handles the failure case differently. In that case, one would need to pass the caller component as a parameter to set different flags in the store.

Dumb example

Page where the user can edit the username in navbar and in /profile page (which contains the navbar). Both trigger an action "change username", which is asynchronous. If the promise fails, the page should only display an error in the component the user was trying to change the username from.

Of course it is a dumb example, but I don't see a way to solve this issue without duplicating code and making the same call in 2 different actions.

-2

actions.js

const axios = require('axios');
const types = require('./types');

export const actions = {
  GET_CONTENT({commit}){
    axios.get(`${URL}`)
      .then(doc =>{
        const content = doc.data;
        commit(types.SET_CONTENT , content);
        setTimeout(() =>{
          commit(types.IS_LOADING , false);
        } , 1000);
      }).catch(err =>{
        console.log(err);
    });
  },
}

home.vue

<script>
  import {value , onCreated} from "vue-function-api";
  import {useState, useStore} from "@u3u/vue-hooks";

  export default {
    name: 'home',

    setup(){
      const store = useStore();
      const state = {
        ...useState(["content" , "isLoading"])
      };
      onCreated(() =>{
        store.value.dispatch("GET_CONTENT" );
      });

      return{
        ...state,
      }
    }
  };
</script>

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.