Vuex: How do we handle failure or success of async requests?

Created on 21 Dec 2015  路  18Comments  路  Source: vuejs/vuex

Imagine I have a simple login.vue component with a username and password field.

I attempt a login by calling store.actions.login(this.username, this.password)

I want to be able to hide a loading overlay, close the login screen, and provide reasons for login failure.

  1. Should I watch a store.state.isAuthenticated variable and react accordingly?
  2. Should the action be returning a promise?
  3. How can I get a return value on error, so that I can display an error message?

Most helpful comment

It's in general better to treat the failure as just another state mutation: if it failed, something should happen, and that should be reflected in the store's state.

An example here: https://github.com/vuejs/vuex/blob/master/examples/shopping-cart/store/actions.js#L6-L14

The buyProduct API call may fail, and when it does, it simply dispatches CHECKOUT_FAILURE instead of CHECKOUT_SUCCESS. And here's the CHECKOUT_FAILURE mutation: https://github.com/vuejs/vuex/blob/master/examples/shopping-cart/store/modules/cart.js#L40-L44

All 18 comments

I'm currently stuck on this myself, my initial thoughts we're to $broadcast a success or failure event so all components can respond how they like but don't have access to the App instance within Vuex.

For now i'm just going to use store.state.response but it feels very wrong.

The way I am doing it is that I return a promise from the action:

// Vuex Store
export const storeActions = {

  someAction(store) {

    // return a promise from an api service
    return someService.someMethod(function(response) {
      store.dispatch(ACTION, response);
    });

  }

}
// Somewhere in your app
const { someAction } = store.actions

someAction().then(function() {
  // request successful, let Vuex mutate the state
}).catch(function() {
  // display error, not related to Vuex
})

I let the mutation change the state but in parallel I also listen to that promise resolve.

But notice, I only use the promise to know the request finished and do something like render a component or display an error. I do not use the response data inside the resolve, all data is still managed by the Vuex store. So for example in your case, I would listen to changes in isAuthenticated to know when the user logged in, but if the promise returns an error, I would then use the response data from the resolve to display the error.
Hope it makes sense, I'm not sure this is the best way, but I can't find a better solution so that`s what I'm doing right now.

It seems to me the whole idea of using a centralized store is to avoid having to broadcast messages.

Promises seem very reasonable as long as we can remember to stay away from directly altering response data. @jonagoldman's approach seems like the best to me at the moment.

I still feel like using promises feels a bit like going backwards somehow. But I can't really bring myself to add new variables to the state that are there just to store error messages and isDone type flags.

It's in general better to treat the failure as just another state mutation: if it failed, something should happen, and that should be reflected in the store's state.

An example here: https://github.com/vuejs/vuex/blob/master/examples/shopping-cart/store/actions.js#L6-L14

The buyProduct API call may fail, and when it does, it simply dispatches CHECKOUT_FAILURE instead of CHECKOUT_SUCCESS. And here's the CHECKOUT_FAILURE mutation: https://github.com/vuejs/vuex/blob/master/examples/shopping-cart/store/modules/cart.js#L40-L44

If your API is promise-based, it also possible to write a simple helper to simplify this - e.g. automatically dispatch mutations based on the returned promise.

@yyx990803 Great explanation. Keep it simple!

But what should I do if I want to redirect to an other page when I was login succeed?

@tiaod if you are using vue-router, then it will have to rely on the work-in-progress integration between the two. If it's a full page jump then it should not be a vuex action.

@yyx990803 thanks for your answer. Yes I'm using vue-router, and finally I just import the router object and use router.go() method to redirect. Am I hacking?:-)

If your API is promise-based, it also possible to write a simple helper to simplify this - e.g. automatically dispatch mutations based on the returned promise.

Hi @yyx990803

Can show a quick example of how you would handle promises with vuex ?
Given that you don't recommand the use of middlewares for such operations.

Thanks a lot :)

@nicolas-t a minimal example:

export function checkout (store, products) {
  api.checkout(products).then(
    () => store.dispatch('SUCCESS'),
    (e) => store.dispatch('FAILED', e)
  )
}

Yes of course,
But in case I have a rest api with douzen of ressources, plus GET, POST, PUT and DELETE calls ?

What's the best way to automate it?
Should I really create three types per couple resource/http_method ?
like :

RESOURCE1_GET_SUCCESS
RESOURCE1_GET_FAIL

RESOURCE1_POST_PENDING
RESOURCE1_POST_SUCCESS
RESOURCE1_POST_FAIL
....

RESOURCE2_GET_PENDING
RESOURCE2_GET_SUCCESS
RESOURCE2_GET_FAIL
...

Each of the ..._PENDING mutations containing some very similar code:
state.resource1[id].status = 'pending'
pretty much the same for every ..._SUCCESS and ..._FAIL mutation.

How would you handle such mutations ? Or do you have any article about this topic (even with flux or redux), I didn't find anything.

Thanks a lot :3

It depends on whether the result status is significant enough to be displayed to your user. If you want to display different messages/interface to the user for each resource, then this is what you need to do; however, if you just want some generic indicator for request pending/success/failure (for example a top loading bar + a toast message), then you can reuse the same set of mutations for all api calls.

Ok thanks ! I'll try something :)

Nicolas,

We make a decision on whether to return something from an action in our project. If the action is to return the promise we strictly say that the promise handling from the component should not further call anything that may mutate state or call further actions.

We allow thing like routing to be handled ect, as this is not always completely state driven. A notification component can be dispatched to for example, however depending on preference the notification component could be built to use the store.

Sometimes actions may have to be returnable, such as to decide routing. It's then acceptable to act upon an action, but in a refined amount. Too much causes complexity.

@yyx990803 I may be wrong, but what's your opinion on this approach.

This discussion reminds me of "Command Query Separation" - http://martinfowler.com/bliki/CommandQuerySeparation.html

Hello guys, im usign a callback function, and its ok for me....

component:

 <div class="progress" v-show="loading">
                            <div class="indeterminate"></div>
</div>
.............
export default{
    data () {
        return {
            username:'',
            password:'',
            loading:false
        }
    },
.....
 methods:    {
        tryLogin(){
            this.loading = true
            let user = {'username':this.username,'password':this.password}
             //doLogin is a action
            this.doLogin(user,()=>{
                this.$data.loading=false;
            })
        }
    }
..............

action:

export function doLogin({dispatch}, user, handler) {
    this.$http.post(`${URI}/auth/login`,user).then((response) => {
           let token = "todo.....";
           dispatch("LOGIN", user, token);
           handler();
      }, (response) => {
        console.log(response);
        message(`Error ${response.data.error`);
        handler();
      });
}

maybe its a good ideia return the message error on handler...

handler(false,response.data.error);

Although this topic was closed, i think this answer is usefull : Vuex Actions in a callback

Was this page helpful?
0 / 5 - 0 ratings