Promessas de retorno das ações da Vuex

130

Recentemente, comecei a migrar coisas do jQ para um framework mais estruturado, o VueJS, e eu adoro isso!

Conceitualmente, a Vuex foi uma mudança de paradigma para mim, mas estou confiante de que sei o que está acontecendo agora e entendo totalmente! Mas existem algumas pequenas áreas cinzentas, principalmente do ponto de vista da implementação.

Este é um projeto bom, mas não sei se contradiz o ciclo Vuex de fluxo de dados unidirecional.

Basicamente, é considerado uma boa prática retornar um objeto de promessa (semelhante a) de uma ação? Trato-os como invólucros assíncronos, com estados de falha e similares, portanto, parece um bom ajuste para retornar uma promessa. Ao contrário, os mutadores apenas mudam as coisas e são as estruturas puras dentro de uma loja / módulo.

Daniel Park
fonte

Respostas:

255

actionsno Vuex são assíncronos. A única maneira de permitir que a função de chamada (iniciador da ação) saiba que uma ação está concluída - é retornando uma promessa e resolvendo-a mais tarde.

Aqui está um exemplo: myActionretorna a Promise, faz uma chamada http e resolve ou rejeita a Promiseúltima - tudo de forma assí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);
            })
        })
    }
}

Agora, quando o componente Vue for iniciado myAction, ele obterá esse objeto Promise e poderá saber se foi bem-sucedido ou não. Aqui está um código de amostra para o 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 você pode ver acima, é altamente benéfico actionsretornar a Promise. Caso contrário, não há como o iniciador de ações saber o que está acontecendo e quando as coisas estão estáveis ​​o suficiente para mostrar algo na interface do usuário.

E uma última observação sobre mutators- como você corretamente apontou, eles são síncronos. Eles mudam as coisas no statee geralmente são chamados de actions. Não há necessidade de misturar Promisescom mutators, como a actionsalça que parte.

Edit: Minhas visões sobre o ciclo Vuex de fluxo de dados unidirecional:

Se você acessar dados como this.$store.state["your data key"]em seus componentes, o fluxo de dados será unidirecional.

A promessa da ação é apenas informar ao componente que a ação está completa.

O componente pode obter dados da função de resolução de promessa no exemplo acima (não unidirecional, portanto, não recomendado) ou diretamente do $store.state["your data key"]qual é unidirecional e segue o ciclo de vida dos dados vuex.

O parágrafo acima assume que o seu mutador usa Vue.set(state, "your data key", http_data), assim que a chamada http for concluída em sua ação.

Mani
fonte
4
"Como você pode ver acima, é altamente benéfico que as ações retornem uma promessa. Caso contrário, não há como o iniciador de ações saber o que está acontecendo e quando as coisas estão estáveis ​​o suficiente para mostrar algo na interface do usuário". IMO, isso está faltando o ponto de Vuex. O iniciador da ação não precisa saber o que está acontecendo. A ação deve alterar o estado quando os dados retornam do evento assíncrono, e o componente deve responder a essa mudança de estágio com base no estado da loja Vuex, não em uma promessa.
ceejayoz
1
@ceejayoz Concordado, o estado deve ser a única fonte de verdade para todos os objetos de dados. Mas a promessa é a única maneira de se comunicar com o iniciador da ação. Por exemplo, se você deseja mostrar um botão "Tentar novamente" após a falha do http, essas informações não podem entrar no estado, mas podem ser comunicadas apenas através de a Promise.reject().
Mani
1
Isso pode ser facilmente tratado na loja Vuex. A ação em si pode disparar um failedmutador que define state.foo.failed = true, que o componente pode manipular. Não é necessário que a promessa seja passada ao componente para isso e, como bônus, qualquer outra coisa que queira reagir à mesma falha também pode ser feita na loja.
precisa
4
@ceejayoz Confira Ações de composição (última seção) nos documentos - vuex.vuejs.org/en/actions.html - as ações são assíncronas e, portanto, retornar uma promessa é uma boa idéia, conforme indicado nesses documentos. Talvez não no caso $ http acima, mas em outros casos, talvez seja necessário saber quando uma ação é concluída.
Mani
6
@DanielPark Sim, "depende" do cenário e das preferências individuais do desenvolvedor. No meu caso, eu queria evitar valores intermediários, como {isLoading:true}no meu estado, e, portanto, recorri às promessas. Suas preferências podem variar. No final do dia, nosso objetivo é escrever um código livre de problemas e de manutenção. A promessa de atingir esse objetivo ou o estado da vuex - é deixada para os desenvolvedores e equipes individuais decidirem.
Mani
41

Apenas para obter informações sobre um tópico fechado: você não precisa criar uma promessa, o axios retorna uma:

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

Exemplo:

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

Outro exemplo:

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

Outro exemplo com 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'
        }
    },
Anoop.PA
fonte
O último exemplo não deve ser redundante, pois as ações de axios já são assíncronas por padrão?
nonNumericalFloat
9

Ações

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
fonte
2
esta resposta não trabalhar indefinido no componente
Nand Lal
1
Eu acho que você esqueceu de acrescentar retorno em função ADD_PRODUCT
Bhaskararao Gummidi
Deve estar em minúsculas "a" em "axios".
Bigp
I tomado Axois como const que está importando de 'Axios'
Bhaskararao Gummidi
0

TL: DR; retornar promessas de suas ações somente quando necessário, mas DRY encadeando as mesmas ações.

Durante muito tempo, também pensei que as ações retornadas contradizem o ciclo Vuex do fluxo de dados unidirecional.

Porém, há CASOS DE BORDA onde o retorno de uma promessa de suas ações pode ser "necessário".

Imagine uma situação em que uma ação possa ser acionada a partir de 2 componentes diferentes e cada um lide com o caso de falha de maneira diferente. Nesse caso, seria necessário passar o componente de chamada como parâmetro para definir diferentes sinalizadores na loja.

Exemplo idiota

Página em que o usuário pode editar o nome de usuário na barra de navegação e na página / perfil (que contém a barra de navegação). Ambos acionam uma ação "alterar nome de usuário", que é assíncrona. Se a promessa falhar, a página deve exibir apenas um erro no componente do qual o usuário estava tentando alterar o nome de usuário.

Claro que é um exemplo idiota, mas não vejo uma maneira de resolver esse problema sem duplicar o código e fazer a mesma chamada em duas ações diferentes.

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