Retour des promesses des actions Vuex

131

J'ai récemment commencé à migrer des choses de jQ vers un framework plus structuré étant VueJS, et j'adore ça!

Conceptuellement, Vuex a été un peu un changement de paradigme pour moi, mais je suis convaincu que je sais de quoi il s'agit maintenant, et je comprends totalement! Mais il existe quelques petites zones grises, principalement du point de vue de la mise en œuvre.

Celui-ci, je pense, est bon de par sa conception, mais je ne sais pas s'il contredit le cycle Vuex du flux de données unidirectionnel.

Fondamentalement, est-il considéré comme une bonne pratique de renvoyer un objet (semblable à une promesse) à partir d'une action? Je les traite comme des wrappers asynchrones, avec des états d'échec et autres, donc cela semble être un bon moyen de retourner une promesse. Au contraire, les mutateurs ne font que changer les choses et sont les structures pures au sein d'un magasin / module.

Daniel Park
la source

Réponses:

256

actionsdans Vuex sont asynchrones. La seule façon de faire savoir à la fonction appelante (initiateur de l'action) qu'une action est terminée - est de renvoyer une promesse et de la résoudre plus tard.

Voici un exemple: myActionrenvoie a Promise, effectue un appel http et résout ou rejette le Promisedernier - le tout de manière asynchrone

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

Maintenant, lorsque votre composant Vue démarre myAction, il obtiendra cet objet Promise et pourra savoir s'il a réussi ou non. Voici un exemple de code pour le composant 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")
        })
    }
}

Comme vous pouvez le voir ci-dessus, il est très avantageux actionsde renvoyer unPromise . Sinon, il n'y a aucun moyen pour l'initiateur de l'action de savoir ce qui se passe et quand les choses sont suffisamment stables pour afficher quelque chose sur l'interface utilisateur.

Et une dernière note concernant mutators- comme vous l'avez souligné à juste titre, ils sont synchrones. Ils changent des éléments dans le state, et sont généralement appelés depuis actions. Il n'y a pas besoin de mélanger Promisesavec mutators, comme le actionsmanche cette partie.

Edit: Mes vues sur le cycle Vuex du flux de données unidirectionnel:

Si vous accédez à des données comme this.$store.state["your data key"] dans vos composants, le flux de données est unidirectionnel.

La promesse de l'action est seulement de faire savoir au composant que l'action est terminée.

Le composant peut soit prendre des données de la fonction de résolution de promesse dans l'exemple ci-dessus (non unidirectionnel, donc non recommandé), soit directement de $store.state["your data key"] desquelles est unidirectionnel et suit le cycle de vie des données vuex.

Le paragraphe ci-dessus suppose que votre mutateur utilise Vue.set(state, "your data key", http_data), une fois que l'appel http est terminé dans votre action.

Mani
la source
4
"Comme vous pouvez le voir ci-dessus, il est très avantageux pour les actions de renvoyer une promesse. Sinon, il n'y a aucun moyen pour l'initiateur de l'action de savoir ce qui se passe et quand les choses sont suffisamment stables pour afficher quelque chose sur l'interface utilisateur." OMI, il manque le point de Vuex. L'initiateur de l'action ne devrait pas avoir besoin de savoir ce qui se passe. L'action doit faire muter l'état lorsque les données reviennent de l'événement asynchrone, et le composant doit répondre à ce changement d'étape en fonction de l'état du magasin Vuex, et non d'une promesse.
ceejayoz
1
@ceejayoz D'accord, l'État devrait être la seule source de vérité pour tous les objets de données. Mais la promesse est le seul moyen de communiquer avec l'initiateur de l'action. Par exemple, si vous souhaitez afficher un bouton «Réessayer» après l'échec de HTTP, ces informations ne peuvent pas entrer dans l'état, mais peuvent être communiquées uniquement via un fichier Promise.reject().
Mani
1
Cela peut facilement être géré dans le magasin Vuex. L'action elle-même peut déclencher un failedmutateur qui définit state.foo.failed = true, que le composant peut gérer. Il n'est pas nécessaire que la promesse soit transmise au composant pour cela, et en prime, tout ce qui veut réagir au même échec peut également le faire depuis le magasin.
ceejayoz
4
@ceejayoz Consultez Composing Actions (dernière section) dans la documentation - vuex.vuejs.org/en/actions.html - les actions sont asynchrones et donc renvoyer une promesse est une bonne idée, comme indiqué dans ces documents. Peut-être pas dans le cas de $ http ci-dessus, mais dans un autre cas, nous pouvons avoir besoin de savoir quand une action est terminée.
Mani
6
@DanielPark Oui, "cela dépend" du scénario et des préférences de chaque développeur. Dans mon cas, j'ai voulu éviter les valeurs intermédiaires comme {isLoading:true}dans mon état, et j'ai donc eu recours aux promesses. Vos préférences peuvent varier. À la fin de la journée, notre objectif est d'écrire du code sans encombrement et maintenable. La décision d'atteindre cet objectif ou l'état de vuex est laissée aux développeurs individuels et aux équipes.
Mani
41

Juste pour une information sur un sujet fermé: vous n'avez pas à créer de promesse, axios en renvoie une lui-même:

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

Exemple:

    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 autre exemple:

    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 autre exemple avec 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'
        }
    },
Anoop.PA
la source
Le dernier exemple ne devrait-il pas être redondant car les actions axios sont par défaut déjà asynchrones?
nonNumericalFloat
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
  });
});

Composant

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
la source
2
cette réponse ne fonctionne pas non définie dans le composant
Nand Lal
1
Je pense que vous avez oublié d'ajouter le retour dans la fonction ADD_PRODUCT
Bhaskararao Gummidi
Doit être en minuscule «a» dans «axios».
bigp
J'ai pris Axois comme const qui importe de 'axios'
Bhaskararao Gummidi
0

TL: DR; ne retournez les promesses de vos actions que lorsque cela est nécessaire, mais DRY enchaînant les mêmes actions.

Pendant longtemps, j'ai également pensé que le retour des actions contredit le cycle Vuex du flux de données unidirectionnel.

Mais, il existe des CAS EDGE où le retour d'une promesse de vos actions pourrait être "nécessaire".

Imaginez une situation où une action peut être déclenchée à partir de 2 composants différents, et chacun gère le cas d'échec différemment. Dans ce cas, il faudrait passer le composant appelant en tant que paramètre pour définir différents indicateurs dans le magasin.

Exemple stupide

Page où l'utilisateur peut modifier le nom d'utilisateur dans la barre de navigation et dans la page / profile (qui contient la barre de navigation). Les deux déclenchent une action "changer le nom d'utilisateur", qui est asynchrone. Si la promesse échoue, la page ne doit afficher qu'une erreur dans le composant à partir duquel l'utilisateur essayait de changer le nom d'utilisateur.

Bien sûr, c'est un exemple stupide, mais je ne vois pas de moyen de résoudre ce problème sans dupliquer le code et faire le même appel dans 2 actions différentes.

srmico
la source
-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>
Chris Michael
la source