Mengembalikan Janji dari tindakan Vuex

131

Saya baru-baru ini mulai memigrasi hal-hal dari jQ ke kerangka kerja yang lebih terstruktur sebagai VueJS, dan saya menyukainya!

Secara konseptual, Vuex telah menjadi sedikit perubahan paradigma bagi saya, tetapi saya yakin saya tahu tentang apa itu semua sekarang, dan benar-benar mengerti! Tapi ada beberapa area abu-abu kecil, kebanyakan dari sudut pandang implementasi.

Yang ini saya rasa bagus secara desain, tetapi tidak tahu apakah itu bertentangan dengan siklus Vuex aliran data uni-directional.

Pada dasarnya, apakah dianggap praktik yang baik untuk mengembalikan objek promise (-seperti) dari suatu tindakan? Saya memperlakukan ini sebagai pembungkus asinkron, dengan keadaan gagal dan sejenisnya, jadi sepertinya cocok untuk membalas janji. Sebaliknya mutator hanya mengubah sesuatu, dan merupakan struktur murni dalam penyimpanan / modul.

Daniel Park
sumber

Jawaban:

256

actionsdi Vuex tidak sinkron. Satu-satunya cara agar fungsi pemanggil (pemrakarsa tindakan) mengetahui bahwa suatu tindakan telah selesai - adalah dengan mengembalikan Janji dan menyelesaikannya nanti.

Berikut ini contohnya: myActionmengembalikan a Promise, membuat panggilan http dan menyelesaikan atau menolak Promisenanti - semua secara asinkron

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

Sekarang, ketika komponen Vue Anda dimulai myAction, ia akan mendapatkan objek Promise ini dan dapat mengetahui apakah berhasil atau tidak. Berikut beberapa contoh kode untuk komponen 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")
        })
    }
}

Seperti yang Anda lihat di atas, sangat bermanfaat untuk actionsmengembalikan file Promise. Jika tidak, tidak ada cara bagi pemrakarsa tindakan untuk mengetahui apa yang terjadi dan kapan semuanya cukup stabil untuk menampilkan sesuatu di antarmuka pengguna.

Dan catatan terakhir tentang mutators- seperti yang Anda tunjukkan dengan benar, mereka sinkron. Mereka mengubah barang di state, dan biasanya dipanggil dari actions. Tidak perlu untuk campuran Promisesdengan mutators, sebagai actionspegangan bagian.

Sunting: Pandangan saya tentang siklus Vuex dari aliran data satu arah:

Jika Anda mengakses data seperti this.$store.state["your data key"]di komponen Anda, maka aliran datanya searah.

Janji dari tindakan hanya untuk memberi tahu komponen bahwa tindakan telah selesai.

Komponen dapat mengambil data dari fungsi penyelesaian janji dalam contoh di atas (bukan uni-directional, oleh karena itu tidak disarankan), atau langsung dari $store.state["your data key"]yang unidirectional dan mengikuti siklus hidup data vuex.

Paragraf di atas mengasumsikan mutator Anda menggunakan Vue.set(state, "your data key", http_data), setelah panggilan http selesai dalam tindakan Anda.

Mani
sumber
4
"Seperti yang Anda lihat di atas, sangat bermanfaat bagi tindakan untuk mengembalikan Janji. Jika tidak, tidak ada cara bagi pemrakarsa tindakan untuk mengetahui apa yang terjadi dan kapan segalanya cukup stabil untuk menampilkan sesuatu di antarmuka pengguna." IMO, ini menghilangkan inti dari Vuex. Pemrakarsa tindakan tidak perlu mengetahui apa yang terjadi. Tindakan tersebut harus mengubah status saat data kembali dari peristiwa asinkron, dan komponen harus merespons perubahan tahapan tersebut berdasarkan status penyimpanan Vuex, bukan Promise.
ceejayoz
1
@ceejayoz Setuju, negara bagian harus menjadi satu-satunya sumber kebenaran untuk semua objek data. Tetapi Janji adalah satu-satunya cara untuk berkomunikasi kembali kepada pemrakarsa tindakan. Misalnya, jika Anda ingin menampilkan tombol "Coba Lagi" setelah http gagal, informasi tersebut tidak dapat masuk ke status, tetapi hanya dapat dikomunikasikan kembali melalui a Promise.reject().
Mani
1
Itu dapat dengan mudah ditangani di dalam toko Vuex. Tindakan itu sendiri dapat mengaktifkan failedmutator yang disetel state.foo.failed = true, yang dapat ditangani oleh komponen. Tidak perlu janji untuk diteruskan ke komponen untuk itu, dan sebagai bonus, apa pun yang ingin bereaksi terhadap kegagalan yang sama dapat melakukannya dari toko juga.
ceejayoz
4
@ceejayoz Lihat Tindakan Penulisan (bagian terakhir) di dokumen - vuex.vuejs.org/en/actions.html - tindakan tidak sinkron dan oleh karena itu mengembalikan Janji adalah ide yang bagus, seperti yang dinyatakan dalam dokumen tersebut. Mungkin tidak dalam kasus $ http di atas, tetapi dalam beberapa kasus lain kita mungkin perlu mengetahui kapan suatu tindakan selesai.
Mani
6
@DanielPark Ya, "itu tergantung" pada skenario dan preferensi pengembang individu. Dalam kasus saya, saya ingin menghindari nilai-nilai menengah seperti {isLoading:true}di negara bagian saya, dan karena itu menggunakan Promises. Preferensi Anda mungkin berbeda. Pada akhirnya, tujuan kami adalah menulis kode yang bebas kekacauan dan dapat dipelihara. Apakah janji mencapai tujuan itu, atau status vuex - diserahkan kepada pengembang individu dan tim untuk memutuskan.
Mani
41

Hanya untuk informasi tentang topik tertutup: Anda tidak perlu membuat janji, axios mengembalikannya sendiri:

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

Contoh:

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

Contoh lain:

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

Contoh lain dengan 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
sumber
Haruskah contoh terakhir tidak berlebihan karena tindakan axios secara default sudah asinkron?
nonNumericalFloat
9

Tindakan

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

Komponen

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
sumber
2
respons tidak berfungsi ini tidak ditentukan dalam komponen
Nand Lal
1
Saya pikir Anda lupa menambahkan pengembalian dalam fungsi ADD_PRODUCT
Bhaskararao Gummidi
Harus huruf kecil "a" di "axios".
Bigp
Saya mengambil Axois sebagai const yang mengimpor dari 'axios'
Bhaskararao Gummidi
0

TL: DR; mengembalikan janji dari tindakan Anda hanya jika diperlukan, tetapi KERING yang merangkai tindakan yang sama.

Untuk waktu yang lama saya juga berpikir bahwa tindakan kembali bertentangan dengan siklus Vuex aliran data satu arah.

Tapi, ada KASUS TEPI di mana mengembalikan janji dari tindakan Anda mungkin "perlu".

Bayangkan situasi di mana suatu tindakan dapat dipicu dari 2 komponen berbeda, dan masing-masing menangani kasus kegagalan secara berbeda. Dalam hal ini, seseorang harus meneruskan komponen pemanggil sebagai parameter untuk menyetel tanda yang berbeda di penyimpanan.

Contoh bodoh

Halaman di mana pengguna dapat mengedit nama pengguna di navbar dan di / halaman profil (yang berisi navbar). Keduanya memicu tindakan "ubah nama pengguna", yang tidak sinkron. Jika promise gagal, halaman seharusnya hanya menampilkan error di komponen tempat pengguna mencoba mengubah nama penggunanya.

Tentu saja ini adalah contoh yang bodoh, tetapi saya tidak melihat cara untuk menyelesaikan masalah ini tanpa menduplikasi kode dan melakukan panggilan yang sama dalam 2 tindakan berbeda.

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