Возврат обещаний из действий Vuex

131

Недавно я начал переносить вещи с jQ на более структурированный фреймворк - VueJS, и мне это нравится!

Концептуально Vuex стал для меня своего рода сдвигом парадигмы, но я уверен, что знаю, о чем идет речь, и полностью понимаю! Но есть несколько небольших серых областей, в основном с точки зрения реализации.

Этот, на мой взгляд, хорош по дизайну, но не знаю, противоречит ли он циклу Vuex однонаправленного потока данных.

В принципе, считается ли хорошей практикой возвращать обещанный (-подобный) объект из действия? Я отношусь к ним как к асинхронным оболочкам с состояниями сбоя и т.п., поэтому мне кажется, что они подходят для возврата обещаний. Напротив, мутаторы просто меняют вещи и представляют собой чистые структуры внутри магазина / модуля.

Дэниэл Парк
источник

Ответы:

256

actionsв Vuex асинхронны. Единственный способ сообщить вызывающей функции (инициатору действия), что действие выполнено, - это вернуть Promise и разрешить его позже.

Вот пример: myActionвозвращает Promise, выполняет http-вызов и разрешает или отклоняет более Promiseпоздний - все асинхронно

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

Теперь, когда ваш компонент Vue запускается myAction, он получит этот объект Promise и сможет узнать, удалось это или нет. Вот пример кода для компонента 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")
        })
    }
}

Как вы можете видеть выше, очень выгодно actionsвернуть домен Promise. В противном случае у инициатора действия не будет возможности узнать, что происходит и когда все достаточно стабильно, чтобы что-то показать в пользовательском интерфейсе.

И последнее замечание относительно mutators- как вы правильно заметили, они синхронны. Они меняют содержимое stateи обычно вызываются из actions. Там нет необходимости смешивать Promisesс mutators, как actionsсправиться с этой частью.

Изменить: Мои взгляды на цикл Vuex однонаправленного потока данных:

Если вы this.$store.state["your data key"]получаете доступ к данным, как в ваших компонентах, то поток данных будет однонаправленным.

Обещание действия состоит только в том, чтобы дать компоненту знать, что действие выполнено.

Компонент может либо получать данные из функции разрешения обещаний в приведенном выше примере (не однонаправленно, поэтому не рекомендуется), либо непосредственно из $store.state["your data key"]которой является однонаправленным и следует жизненному циклу данных vuex.

В приведенном выше абзаце предполагается, что ваш мутатор использует Vue.set(state, "your data key", http_data)после завершения http-вызова в вашем действии.

Mani
источник
4
«Как вы можете видеть выше, для действий очень выгодно возвращать обещание. В противном случае инициатор действия не сможет узнать, что происходит и когда все достаточно стабильно, чтобы показать что-то в пользовательском интерфейсе». ИМО, здесь отсутствует суть Vuex. Инициатору действия не нужно знать, что происходит. Действие должно изменять состояние, когда данные возвращаются из асинхронного события, и компонент должен реагировать на это изменение этапа на основе состояния хранилища Vuex, а не обещания.
ceejayoz,
1
@ceejayoz Согласен, государство должно быть единственным источником истины для всех объектов данных. Но обещание - это единственный способ связаться с инициатором действия. Например, если вы хотите показать кнопку «Попробовать снова» после сбоя http, эта информация не может перейти в состояние, но может быть передана обратно только через Promise.reject().
Mani
1
Это можно легко сделать в магазине Vuex. Само действие может failedзапускать мутатор, который устанавливает state.foo.failed = true, с которым компонент может справиться. Нет необходимости в обещании не должны быть переданы в компонент для этого, и в качестве бонуса, все остальное , что хочет , чтобы реагировать на тот же провал может сделать это из магазина тоже.
ceejayoz
4
@ceejayoz Ознакомьтесь с составлением действий (последний раздел) в документации - vuex.vuejs.org/en/actions.html - действия асинхронны, поэтому, как указано в этих документах, рекомендуется возвращать Promise. Возможно, не в случае с $ http выше, но в другом случае нам может потребоваться знать, когда действие завершено.
Mani
6
@DanielPark Да, «это зависит» от сценария и индивидуальных предпочтений разработчика. В моем случае я хотел избежать промежуточных значений, как {isLoading:true}в моем состоянии, и поэтому прибег к обещаниям. Ваши предпочтения могут отличаться. В конце концов, наша цель - написать простой и удобный в сопровождении код. Достигает ли обещание этой цели или состояние vuex - решать отдельным разработчикам и командам.
Мани
41

Просто для информации по закрытой теме: вам не нужно создавать обещание, axios возвращает его сам:

Ссылка: https://forum.vuejs.org/t/how-to-resolve-a-promise-object-in-a-vuex-action-and-redirect-to-another-route/18254/4

Пример:

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

Другой пример:

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

Другой пример с 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
источник
Не должен ли последний пример быть избыточным, поскольку действия axios по умолчанию уже асинхронны?
nonNumericalFloat
9

действия

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

Составная часть

this.$store.dispatch('ADD_PRODUCT',data).then((res) => {
  if (res.status === 'success') {
    // write your success actions here....
  } else {
     // write your error actions here...
  }
})
Бхаскарарао Гуммиди
источник
2
этот неработающий ответ в компоненте undefined
Нанд Лал
1
Я думаю, вы забыли добавить return в функцию ADD_PRODUCT
Бхаскарарао Гуммиди
В «аксиомах» должна быть строчная «а».
bigp
Я принял Axois как const, который импортируется из 'axios'
Бхаскарарао Гуммиди
0

TL: DR; возвращать обещания от ваших действий только тогда, когда это необходимо, но СУХОЙ цепочкой тех же действий.

Долгое время я также думал, что возвращаемые действия противоречат циклу Vuex однонаправленного потока данных.

Но есть КРАЙНЫЕ СЛУЧАИ, когда возврат обещания из ваших действий может быть «необходимым».

Представьте себе ситуацию, когда действие может быть инициировано двумя разными компонентами, и каждый по-своему обрабатывает случай сбоя. В этом случае необходимо передать вызывающий компонент в качестве параметра для установки разных флагов в магазине.

Тупой пример

Страница, на которой пользователь может редактировать имя пользователя на панели навигации и на странице / profile (которая содержит панель навигации). Оба инициируют действие «изменить имя пользователя», которое является асинхронным. Если обещание не выполняется, на странице должна отображаться ошибка только в том компоненте, с которого пользователь пытался изменить имя пользователя.

Конечно, это глупый пример, но я не вижу способа решить эту проблему без дублирования кода и выполнения одного и того же вызова в двух разных действиях.

srmico
источник
-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>
Крис Майкл
источник