Restituzione di promesse da azioni Vuex


130

Di recente ho iniziato a migrare cose da jQ a un framework più strutturato essendo VueJS, e lo adoro!

Concettualmente, Vuex è stato un po 'un cambio di paradigma per me, ma sono fiducioso di sapere di cosa si tratta ora e lo capisco perfettamente! Ma esistono alcune piccole aree grigie, principalmente dal punto di vista dell'implementazione.

Questo mi sembra buono per progettazione, ma non so se contraddice il ciclo Vuex del flusso di dati unidirezionale.

Fondamentalmente, è considerata una buona pratica restituire un oggetto promettente (simile a) da un'azione? Considero questi come involucri asincroni, con stati di fallimento e simili, quindi sembra una buona misura per restituire una promessa. I mutatori al contrario cambiano semplicemente le cose e sono le pure strutture all'interno di un negozio / modulo.

Risposte:


255

actionsin Vuex sono asincroni. L'unico modo per far sapere alla funzione chiamante (iniziatore dell'azione) che un'azione è completa è restituire una Promessa e risolverla in un secondo momento.

Ecco un esempio: myActionrestituisce a Promise, effettua una chiamata http e risolve o rifiuta la Promisesuccessiva, il tutto in modo asincrono

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);
            })
        })
    }
}

Ora, quando il componente Vue viene avviato myAction, otterrà questo oggetto Promise e potrà sapere se ha avuto successo o meno. Ecco un codice di esempio per il componente Vue:

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")
        })
    }
}

Come puoi vedere sopra, è molto utile actionsrestituire a Promise. Altrimenti non c'è modo per l'iniziatore dell'azione di sapere cosa sta succedendo e quando le cose sono abbastanza stabili da mostrare qualcosa sull'interfaccia utente.

E un'ultima nota riguardante mutators- come hai giustamente sottolineato, sono sincrone. Cambiano roba nel state, e di solito sono chiamati da actions. Non è necessario mescolarsi Promisescon mutators, come actionsgestire quella parte.

Modifica: le mie opinioni sul ciclo Vuex del flusso di dati unidirezionale:

Se accedi ai dati come this.$store.state["your data key"]nei tuoi componenti, il flusso di dati è unidirezionale.

La promessa dell'azione è solo quella di far sapere al componente che l'azione è completa.

Il componente può prendere i dati dalla funzione di risoluzione promessa nell'esempio sopra (non unidirezionale, quindi non raccomandato), o direttamente dal $store.state["your data key"]quale è unidirezionale e segue il ciclo di vita dei dati vuex.

Il paragrafo precedente presuppone che il tuo mutatore utilizzi Vue.set(state, "your data key", http_data), una volta completata la chiamata http nella tua azione.


4
"Come puoi vedere sopra, è molto utile che le azioni restituiscano una Promessa. Altrimenti non c'è modo per l'iniziatore di azioni di sapere cosa sta succedendo e quando le cose sono abbastanza stabili da mostrare qualcosa sull'interfaccia utente." IMO, a questo manca il punto di Vuex. L'iniziatore dell'azione non dovrebbe aver bisogno di sapere cosa sta succedendo. L'azione dovrebbe mutare lo stato quando i dati tornano dall'evento asincrono e il componente dovrebbe rispondere a quel cambiamento di fase in base allo stato del negozio Vuex, non a una promessa.
Ceejayoz,

1
@ceejayoz D'accordo, lo stato dovrebbe essere l'unica fonte di verità per tutti gli oggetti dati. Ma la Promessa è l'unico modo per comunicare all'iniziatore dell'azione. Ad esempio, se si desidera mostrare un pulsante "Riprova" dopo un errore http, tali informazioni non possono andare in stato, ma possono essere comunicate solo tramite a Promise.reject().
Mani,

1
Questo può essere facilmente gestito all'interno del negozio Vuex. L'azione stessa può failedattivare un mutatore che state.foo.failed = truepuò essere gestito dal componente. Non è necessario che la promessa venga trasferita al componente per questo, e come bonus, qualsiasi altra cosa che vuole reagire allo stesso fallimento può farlo anche dal negozio.
Ceejayoz,

4
@ceejayoz Scopri le azioni di composizione (ultima sezione) nei documenti - vuex.vuejs.org/en/actions.html - le azioni sono asincrone e quindi restituire una Promessa è una buona idea, come indicato in questi documenti. Forse non nel caso $ http sopra, ma in alcuni altri casi potremmo aver bisogno di sapere quando un'azione è completata.
Mani,

6
@DanielPark Sì, "dipende" dallo scenario e dalle preferenze dei singoli sviluppatori. Nel mio caso, volevo evitare valori intermedi come {isLoading:true}nel mio stato, e quindi ricorsi alle promesse. Le tue preferenze possono variare. Alla fine della giornata, il nostro obiettivo è scrivere codice privo di ingombri e gestibile. Se la promessa raggiunge tale obiettivo, o lo stato di Vuex, è lasciato ai singoli sviluppatori e team di decidere.
Mani

41

Solo per informazioni su un argomento chiuso: non è necessario creare una promessa, axios restituisce uno stesso:

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

Esempio:

    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 });
        })
    }

Un altro esempio:

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

Un altro esempio con async-waitit

    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'
        }
    },

L'ultimo esempio non dovrebbe essere ridondante poiché le azioni axios sono per impostazione predefinita già asincrone?
nonNumericalFloat

9

Azioni

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
  });
});

Componente

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})

2
questa risposta non funzionante non definita nel componente
Nand Lal,

1
Penso che hai dimenticato di aggiungere il ritorno nella funzione ADD_PRODUCT
Bhaskararao Gummidi,

Dovrebbe essere minuscola "a" in "axios".
bigp,

Ho preso Axois come const che sta importando da 'axios'
Bhaskararao Gummidi

0

TL: DR; restituisci promesse dalle tue azioni solo quando necessario, ma ASCIUTTA concatenando le stesse azioni.

Per molto tempo ho anche pensato che le azioni di ritorno contraddicono il ciclo Vuex del flusso di dati unidirezionale.

Ma ci sono CASI DI BORDO in cui restituire una promessa dalle tue azioni potrebbe essere "necessario".

Immagina una situazione in cui un'azione può essere innescata da 2 diversi componenti e ognuno gestisce il caso di errore in modo diverso. In tal caso, bisognerebbe passare il componente chiamante come parametro per impostare flag diversi nel negozio.

Esempio stupido

Pagina in cui l'utente può modificare il nome utente nella barra di navigazione e nella pagina / profile (che contiene la barra di navigazione). Entrambi attivano un'azione "cambia nome utente", che è asincrona. Se la promessa non riesce, la pagina dovrebbe visualizzare solo un errore nel componente da cui l'utente stava cercando di cambiare il nome utente.

Ovviamente è un esempio stupido, ma non vedo un modo per risolvere questo problema senza duplicare il codice ed effettuare la stessa chiamata in 2 azioni diverse.


-1

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>
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.