Search code examples
javascriptvue.jspromisees6-promisevuex

Break Promise in Vuex Action on API error


This is probably an issue of using the wrong pattern to accomplish what I'm needing, so either an answer of what pattern (and what that pattern is) I should be using or what would solve my current pattern would be greatly appreciated!

I have use case where I need to wait for 1 to many API calls to successfully complete before routing the user to a different page/view... Here's what I'm doing:

// Vuex action
[POST_NEW_INVOICE_UPDATE]: ({ commit }, update) => {
    return apiEndpoints
      .postNewInvoiceUpdate(update)
      .then(() => {
        app.$Progress.finish()
        app.$Toastr.success('Invoice flagged')
      })
      .catch((error) => {
        console.error(error)
        app.$Progress.fail()
        app.$Toastr.error('There was an issue submitting your request', 'Request error')
      })
},
// In component
flag () {
    // ... simplifying for TLDR
    const promises = []
    flagSelections.forEach(selection => {
        // ... simplifying for TLDR
        promises.push(this.$store.dispatch('POST_NEW_INVOICE_UPDATE', parameters))
    })

    Promise.all(promises)
        .then(() => {
            // Route to next invoice in previous search, if search exists
            // ... simplifying for TLDR
        })
        .catch((error) => {
            console.error(error)
        })
},

I'm creating an array of the action, POST_NEW_INVOICE_UPDATE, and calling Promise.all on that array. I want the .then chain to break when an error is caught by the .catch in the action, becuase I don't want to route my users until the APIs have been successful.

I believe I read in MDN that .then continues to chain even if an error is caught, but I obviously need something to prevent my user from being routed before the APIs are all successful.

I imagine I'm just using a dumb, self-created pattern and am not using Vuex actions correctly, so any help in the right direction would be appreciated!

EDIT: I forgot something important! If I take the code from the action and put it in promises.push instead of the dispatch, the code operates as needed, but I hate breaking the pattern of using Vuex for a single use-case.


Solution

  • First off, I wouldn't use Vuex purely to make API calls. The purpose of Vuex is to manage a global state, and I can see here that you're not even using commit to mutate the state at all. You can abstract your API into your own objects or functions without needing Vuex. But it's possible you just trimmed the code down and you actually are calling commit, so I'll stick with that assumption.

    Furthermore, it's anti-pattern for Vuex to be making calls like app.$Progress.fail(). Vuex should know nothing about your components or UI, its sole job is to manage global state.

    Here's how I would structure it:

    async flag() {
      const promises = [];
    
      flagSelections.forEach(selection => {
        promises.push(postNewInvoiceUpdate(parameters));
      });
    
      try {
        await Promise.all(promises);
        app.$Progress.finish();
        app.$Toastr.success('Invoice flagged');
      } catch (e) {
        console.error(error);
        app.$Progress.fail();
        app.$Toastr.error(
          'There was an issue submitting your request',
          'Request error'
        );
      }
    },
    

    If any of the Promises fail, the code in the catch block will be executed. Promise.all means all Promises must resolve, not just some of them. And I would just import postNewInvoiceUpdate from a separate module. There's no need to use Vuex for this.