Devolviendo promesas de acciones de Vuex

130

Recientemente comencé a migrar cosas de jQ a un marco más estructurado que es VueJS, ¡y me encanta!

Conceptualmente, Vuex ha sido un cambio de paradigma para mí, pero estoy seguro de que sé de qué se trata ahora, ¡y lo entiendo totalmente! Pero existen algunas pequeñas áreas grises, principalmente desde el punto de vista de la implementación.

Este creo que es bueno por diseño, pero no sé si contradice el ciclo Vuex del flujo de datos unidireccional.

Básicamente, ¿se considera una buena práctica devolver un objeto prometedor (similar) de una acción? Los trato como envoltorios asíncronos, con estados de falla y similares, por lo que parece una buena opción para devolver una promesa. Al contrario, los mutadores simplemente cambian las cosas y son estructuras puras dentro de una tienda / módulo.

Daniel Park
fuente

Respuestas:

255

actionsen Vuex son asíncronos. La única forma de permitir que la función de llamada (iniciador de la acción) sepa que una acción está completa es devolviendo una Promesa y resolviéndola más tarde.

Aquí hay un ejemplo: myActiondevuelve un Promise, realiza una llamada http y resuelve o rechaza el Promiseposterior, todo de forma asíncrona

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

Ahora, cuando se inicia su componente Vue myAction, obtendrá este objeto Promise y podrá saber si tuvo éxito o no. Aquí hay un código de muestra para el 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")
        })
    }
}

Como puede ver arriba, es altamente beneficioso actionsdevolver a Promise. De lo contrario, el iniciador de acciones no puede saber qué está sucediendo y cuándo las cosas son lo suficientemente estables como para mostrar algo en la interfaz de usuario.

Y una última nota con respecto a mutators- como acertadamente señaló, son sincrónicos. Cambian cosas en el state, y generalmente se les llama desde actions. No hay necesidad de mezclar Promisescon mutators, como el actionsmango esa parte.

Editar: Mis puntos de vista sobre el ciclo Vuex del flujo de datos unidireccional:

Si accede a datos como this.$store.state["your data key"]en sus componentes, entonces el flujo de datos es unidireccional.

La promesa de la acción es solo hacer que el componente sepa que la acción está completa.

El componente puede tomar datos de la función de resolución de promesa en el ejemplo anterior (no unidireccional, por lo tanto no se recomienda), o directamente desde el $store.state["your data key"]cual es unidireccional y sigue el ciclo de vida de los datos vuex.

El párrafo anterior supone que su mutador utiliza Vue.set(state, "your data key", http_data), una vez que se complete la llamada http en su acción.

Mani
fuente
44
"Como puede ver arriba, es muy beneficioso que las acciones devuelvan una Promesa. De lo contrario, el iniciador de acciones no puede saber qué está sucediendo y cuándo las cosas son lo suficientemente estables como para mostrar algo en la interfaz de usuario". En mi opinión, esto está perdiendo el punto de Vuex. El iniciador de la acción no debería necesitar saber qué está sucediendo. La acción debe mutar el estado cuando los datos vuelven del evento asíncrono, y el componente debe responder a ese cambio de etapa en función del estado del almacén Vuex, no una Promesa.
ceejayoz
1
@ceejayoz De acuerdo, el estado debería ser la única fuente de verdad para todos los objetos de datos. Pero la Promesa es la única forma de comunicarse con el iniciador de la acción. Por ejemplo, si desea mostrar un botón "Intentar de nuevo" después de un error de http, esa información no puede entrar en estado, sino que solo puede comunicarse a través de a Promise.reject().
Mani
1
Eso se puede manejar fácilmente dentro de la tienda Vuex. La acción en sí puede disparar un failedmutador que establece state.foo.failed = true, que el componente puede manejar. No es necesario que la promesa pase al componente para eso, y como beneficio adicional, cualquier otra cosa que quiera reaccionar ante el mismo fallo también puede hacerlo desde la tienda.
ceejayoz
44
@ceejayoz Consulte Acciones de redacción (última sección) en los documentos: vuex.vuejs.org/en/actions.html : las acciones son asíncronas y, por lo tanto, devolver una Promesa es una buena idea, como se indica en esos documentos. Tal vez no en el caso $ http anterior, pero en algún otro caso es posible que necesitemos saber cuándo se completa una acción.
Mani
66
@DanielPark Sí, "depende" del escenario y las preferencias individuales del desarrollador. En mi caso, quería evitar valores intermedios como {isLoading:true}en mi estado y, por lo tanto, recurrí a Promises. Sus preferencias pueden variar. Al final del día, nuestro objetivo es escribir código libre de desorden y mantenible. Si la promesa logra ese objetivo, o el estado vuex, se deja a los desarrolladores individuales y a los equipos para decidir.
Mani
41

Solo para obtener información sobre un tema cerrado: no tiene que crear una promesa, axios devuelve una:

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

Ejemplo:

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

Otro ejemplo:

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

Otro ejemplo con 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
fuente
¿El último ejemplo no debería ser redundante ya que las acciones axios son por defecto ya asíncronas?
nonNumericalFloat
9

Comportamiento

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...
  }
})
Bhaskararao Gummidi
fuente
2
esta respuesta no funciona indefinida en el componente
Nand Lal
1
Creo que olvidó agregar el retorno en la función ADD_PRODUCT
Bhaskararao Gummidi
Debe estar en minúscula "a" en "axios".
bigp
Tomé a Axois como constante que está importando de 'axios'
Bhaskararao Gummidi
0

TL: DR; devolver promesas de sus acciones solo cuando sea necesario, pero SECO encadenando las mismas acciones.

Durante mucho tiempo también pensé que las acciones de retorno contradicen el ciclo Vuex del flujo de datos unidireccional.

Pero, hay CASOS DE EDGE en los que devolver una promesa de sus acciones podría ser "necesario".

Imagine una situación en la que una acción se puede activar desde 2 componentes diferentes, y cada uno maneja el caso de falla de manera diferente. En ese caso, uno tendría que pasar el componente llamante como parámetro para establecer diferentes indicadores en la tienda.

Ejemplo tonto

Página donde el usuario puede editar el nombre de usuario en la barra de navegación y en la página / perfil (que contiene la barra de navegación). Ambos desencadenan una acción "cambiar nombre de usuario", que es asíncrona. Si la promesa falla, la página solo debería mostrar un error en el componente desde el que el usuario intentaba cambiar el nombre de usuario.

Por supuesto, es un ejemplo tonto, pero no veo una manera de resolver este problema sin duplicar el código y hacer la misma llamada en 2 acciones diferentes.

srmico
fuente
-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
fuente