Versprechen von Vuex-Aktionen zurückgeben

130

Ich habe kürzlich angefangen, Dinge von jQ auf ein strukturierteres Framework zu migrieren, nämlich VueJS, und ich liebe es!

Konzeptionell war Vuex für mich ein Paradigmenwechsel, aber ich bin zuversichtlich, dass ich weiß, worum es jetzt geht, und verstehe es total! Es gibt jedoch einige kleine Grauzonen, hauptsächlich vom Standpunkt der Implementierung aus.

Dieser ist meiner Meinung nach von Natur aus gut, weiß aber nicht, ob er dem Vuex- Zyklus des unidirektionalen Datenflusses widerspricht .

Wird es grundsätzlich als gute Praxis angesehen, ein versprochenes (ähnliches) Objekt aus einer Aktion zurückzugeben? Ich behandle diese als asynchrone Wrapper mit Fehlerzuständen und dergleichen. Es scheint also eine gute Gelegenheit zu sein, ein Versprechen zurückzugeben. Im Gegensatz dazu ändern Mutatoren nur Dinge und sind die reinen Strukturen innerhalb eines Geschäfts / Moduls.

Daniel Park
quelle

Antworten:

254

actionsin Vuex sind asynchron. Die einzige Möglichkeit, die aufrufende Funktion (Initiator der Aktion) wissen zu lassen, dass eine Aktion abgeschlossen ist, besteht darin, ein Versprechen zurückzugeben und es später zu lösen.

Hier ein Beispiel: myActionGibt a zurück Promise, führt einen http-Aufruf durch und löst den Promisespäteren auf oder lehnt ihn ab - alles asynchron

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

Wenn Ihre Vue-Komponente jetzt gestartet myActionwird, erhält sie dieses Promise-Objekt und kann feststellen, ob es erfolgreich war oder nicht. Hier ist ein Beispielcode für die Vue-Komponente:

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

Wie Sie oben sehen können, ist es sehr vorteilhaft actions, a zurückzugeben Promise. Andernfalls kann der Aktionsinitiator nicht wissen, was passiert und wann die Dinge stabil genug sind, um etwas auf der Benutzeroberfläche anzuzeigen.

Und noch ein letzter Hinweis zu mutators- wie Sie zu Recht betont haben, sind sie synchron. Sie ändern Sachen in der stateund werden normalerweise von angerufen actions. Es besteht keine Notwendigkeit, Promisesmit mutatorsdem actionsTeil dieses Teils zu mischen .

Bearbeiten: Meine Ansichten zum Vuex-Zyklus des unidirektionalen Datenflusses:

Wenn Sie wie this.$store.state["your data key"]in Ihren Komponenten auf Daten zugreifen , ist der Datenfluss unidirektional.

Das Versprechen der Aktion besteht nur darin, der Komponente mitzuteilen, dass die Aktion abgeschlossen ist.

Die Komponente kann entweder Daten aus der Versprechungsauflösungsfunktion im obigen Beispiel entnehmen (nicht unidirektional, daher nicht empfohlen) oder direkt von $store.state["your data key"]dieser unidirektional sein und dem vuex-Datenlebenszyklus folgen.

Der obige Absatz setzt voraus, dass Ihr Mutator verwendet Vue.set(state, "your data key", http_data), sobald der http-Aufruf in Ihrer Aktion abgeschlossen ist.

Mani
quelle
4
"Wie Sie oben sehen können, ist es für Aktionen von großem Vorteil, ein Versprechen zurückzugeben. Andernfalls kann der Aktionsinitiator nicht wissen, was passiert und wann die Dinge stabil genug sind, um etwas auf der Benutzeroberfläche anzuzeigen." IMO, hier fehlt der Punkt von Vuex. Der Aktionsinitiator sollte nicht wissen müssen , was passiert. Die Aktion sollte den Status ändern, wenn Daten vom asynchronen Ereignis zurückkommen, und die Komponente sollte auf diese Phasenänderung basierend auf dem Status des Vuex-Speichers reagieren, nicht auf einem Versprechen.
Ceejayoz
1
@ceejayoz Einverstanden, der Zustand sollte die einzige Quelle der Wahrheit für alle Datenobjekte sein. Das Versprechen ist jedoch die einzige Möglichkeit, dem Aktionsinitiator eine Rückmeldung zu geben. Wenn Sie beispielsweise nach einem http-Fehler die Schaltfläche "Erneut versuchen" anzeigen möchten, können diese Informationen nicht in den Status versetzt werden, sondern können nur über a zurückgegeben werden Promise.reject().
Mani
1
Dies kann problemlos im Vuex-Store erledigt werden. Die Aktion selbst kann einen failedMutator auslösen, der festlegt state.foo.failed = true, mit dem die Komponente umgehen kann. Dafür muss das Versprechen nicht an die Komponente weitergegeben werden, und als Bonus kann alles andere , das auf denselben Fehler reagieren möchte, dies auch im Geschäft tun.
Ceejayoz
4
@ceejayoz Überprüfen Sie das Erstellen von Aktionen (letzter Abschnitt) in den Dokumenten - vuex.vuejs.org/en/actions.html - Aktionen sind asynchron und daher ist es eine gute Idee, ein Versprechen zurückzugeben, wie in diesen Dokumenten angegeben. Vielleicht nicht im obigen Fall $ http, aber in einem anderen Fall müssen wir möglicherweise wissen, wann eine Aktion abgeschlossen ist.
Mani
6
@DanielPark Ja, "es hängt vom Szenario und den individuellen Entwicklerpräferenzen ab". In meinem Fall wollte ich Zwischenwerte wie {isLoading:true}in meinem Bundesstaat vermeiden und griff daher auf Versprechen zurück. Ihre Vorlieben können variieren. Letztendlich ist es unser Ziel, übersichtlichen und wartbaren Code zu schreiben. Ob das Versprechen dieses Ziel erreicht oder der Vuex-Status, bleibt den einzelnen Entwicklern und Teams überlassen.
Mani
41

Nur zur Information zu einem geschlossenen Thema: Sie müssen kein Versprechen erstellen, axios gibt eines selbst zurück:

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

Beispiel:

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

Ein anderes Beispiel:

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

Ein weiteres Beispiel mit Async-Warten

    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'
        }
    },
Anoop.PA
quelle
Sollte das letzte Beispiel nicht redundant sein, da Axios-Aktionen standardmäßig bereits asynchron sind?
nonNumericalFloat
9

Aktionen

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

Komponente

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})
Bhaskararao Gummidi
quelle
2
Diese nicht funktionierende Antwort ist in der Komponente nicht definiert
Nand Lal
1
Ich denke, Sie haben vergessen, Rückkehr in ADD_PRODUCT-Funktion hinzuzufügen
Bhaskararao Gummidi
Sollte in "Axios" in Kleinbuchstaben "a" stehen.
Bigp
Ich nahm Axois als Konstante, die aus 'Axios' importiert
Bhaskararao Gummidi
0

TL: DR; Geben Sie Versprechen von Ihren Aktionen nur zurück, wenn dies erforderlich ist, aber DRY verkettet dieselben Aktionen.

Lange Zeit habe ich auch gedacht, dass die Rückgabe von Aktionen dem Vuex-Zyklus des unidirektionalen Datenflusses widerspricht.

Es gibt jedoch EDGE-FÄLLE, in denen die Rückgabe eines Versprechens aus Ihren Handlungen möglicherweise "notwendig" ist.

Stellen Sie sich eine Situation vor, in der eine Aktion von zwei verschiedenen Komponenten ausgelöst werden kann und jede den Fehlerfall unterschiedlich behandelt. In diesem Fall müsste die Aufruferkomponente als Parameter übergeben werden, um verschiedene Flags im Speicher zu setzen.

Dummes Beispiel

Seite, auf der der Benutzer den Benutzernamen in der Navigationsleiste und auf der Profilseite (die die Navigationsleiste enthält) bearbeiten kann. Beide lösen eine Aktion "Benutzername ändern" aus, die asynchron ist. Wenn das Versprechen fehlschlägt, sollte auf der Seite nur ein Fehler in der Komponente angezeigt werden, von der der Benutzer den Benutzernamen ändern wollte.

Natürlich ist es ein dummes Beispiel, aber ich sehe keine Möglichkeit, dieses Problem zu lösen, ohne Code zu duplizieren und denselben Aufruf in zwei verschiedenen Aktionen auszuführen.

srmico
quelle
-1

action.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>
Chris Michael
quelle