• Stars
    star
    159
  • Rank 235,916 (Top 5 %)
  • Language
    JavaScript
  • License
    MIT License
  • Created almost 8 years ago
  • Updated over 5 years ago

Reviews

There are no reviews yet. Be the first to send feedback to the community and the maintainers!

Repository Details

Better Vuex Action To Simplify Your Async Flow Process And Code Testing

Vuex Saga

Better Vuex Action To Simplify Your Async Flow Process And Code Testing. It's inspired by redux-saga but it works differently. Vuex Saga just simplify the action for async and testing while redux-saga is being advance async flow control which can make some watchers.

DEMO

Installation

You can import vuex-saga.js to your vue component file like this and process it with your preprocessor.

You can install it via NPM

npm install vuex-saga

Dependencies

You need to install babel-polyfill and babel regenerator plugin and put it in the first line of your main entry file to make it works. You can check the example here.

npm install babel-polyfill babel-plugin-transform-regenerator

And Don't forget to add the plugin to your .babelrc

{
  "plugins": ["transform-regenerator"]
}

And Install it as a Vue Plugin like this.

import Vue from 'vue';
import Vuex from 'vuex';
import VuexSaga from 'vuex-saga';

// Make A Vuex Store
Vue.use(Vuex)
const store = new Vuex.Store({
  modules: { /* Some Modules */ }
})

// Install it by pass your store to be an option argument (Since v0.1.0)
Vue.use(VuexSaga, { store: store })

Why I Need This?

Probably you don't need it. But in some cases you'll find a busy async process that you'll hard to organize with ordinary Promise function. For example:

import api from '../api'

// Variable for saving the responses
let product, seller, statistic;

api.fetchProduct()
.then((res) => {
  product = res
  return api.fetchSeller(product.id)
})
.then((res) => {
  seller = res
  return api.statistic(product, seller)
})
.then((res) => {
  statistic = res
  return api.needStatisticProductAndSeller(statistic, product, seller)
})

Or you can skip the let declaration

// source: https://codepen.io/aurelien-bottazini/pen/VPQLBp?editors=0011

const api = {
  fetchProduct() { return Promise.resolve({ id: 'productId'}) },
  fetchSeller(id) { return Promise.resolve('seller') },
  statistic(product, seller) { return  Promise.resolve('stats') },
  needStatisticProductAndSeller(statistic, product, seller) {
    return Promise.resolve('finalResult')
  },
};

api.fetchProduct()
.then((product) => api.fetchSeller(product.id)
      .then((seller) => ({ product, seller })))
.then(({ product, seller }) => api.statistic(product, seller)
      .then((statistic) => ({ product, seller, statistic })))
.then(({ product, seller, statistic }) =>  api.needStatisticProductAndSeller(statistic, product, seller))
.then(console.log);

The solution is pretty simple, You can use async/await

// source: https://forum.vuejs.org/t/let-s-write-better-vuex-action-with-vuex-saga/5527/2

import api from '../api'

async function do () {
  const product = await api.fetchProduct()
  const seller = await api.fetchSeller(product.id)
  const statistic = await api.statistic(product, seller)
  const res = await api.needStatisticProductAndSeller(statistic , product, seller)
  // ...
}

You could use aync/await which are compatible with Promises. You can easily do that with Babel or natively in Chrome and Opera. Firefox and Edge support is coming in their next versions (FX 52, Edge 15). But another point that you should notice is "How can you test it Effortlessly?". For now, I have no idea to test async/await function.

How About Vuex Saga?

According to our cases above, we can simplify that code with Generator Function. It will make our async code looks like synchronous code. Take a look:

import api from '../api'

function *fetchFlow() {
  let product = yield call(api.fetchProduct)
  let seller = yield call(api.fetchSeller, { product })
  let statistic = yield call(api.statistic, { product, seller })
  let lastFetch = yield call(api.needStatisticProductAndSeller, { statistic, product, seller })
  return lastFetch
}

Pretty simple right? It works like async/await function. But You'll get a better testing process although your testing a deep promise function. Take a peek:

import { call } from 'vuex-saga'
import api from '../api'
import { fetchFlow } from '../actions';
import assert from 'assert';

describe('fetchFlow()', function () {

  it('Should Run The Flow Correctly ', function () {
    let process = fetchFlow()

    let fakeRespon = {}

    assert.deepEqual(process.next().value, call(api.fetchProduct))
    assert.deepEqual(process.next(fakeRespon).value, call(api.fetchSeller, { fakeRespon }))
    assert.deepEqual(process.next(fakeRespon).value, call(api.statistic, { fakeRespon }))
    assert.deepEqual(process.next(fakeRespon).value, call(api.needStatisticProductAndSeller, { fakeRespon }))
  });

});

Wait? Are you sure it's a valid testing process? I'm not sure yet. But It works. You don't need to mock the promises, You don't need run the real fetch function in the browser, It just works. Let me tell you how call() function works.

call() function is just an ordinary function that return a plain object contains our real function. So, the generator only pass the plain object while the runner excute the function from the object. Since we don't use the runner, we can test our code like the example above, Just need to deep compare two object.

How About Nested Sagas?

It's just the same, you can wrap it with call() function.

import { call } from 'vuex-saga'
import api from '../api'

function *nestedGenFunc() {
  yield call(delay, 1000)
  return 1000
}

function *fetchFlow() {
  let nested = yield call(nestedGenFunc)
}

Is it take care some parallel async process?

Yes, it should. Just wrap it within an array! Check it out.

import { call } from 'vuex-saga'
import api from '../api'

function *fetchFlow() {
  let [product, other] = yield [call(api.fetchProduct), call(api.otherApis)]
}

The limitation is, you can't run nested generator function in parallel but you can still run some promises or ordinary function (to fetch data or something) in parallel.

How to bind the saga (generator function) to run?

Vuex Saga has a method named sagaRun() which will bind the saga to run. You simple import it, but the recommended way is bind it from the component. We have a helper methods to do it.

<template>
  <div>
    <h1>Hello World</h1>
  </div>
</template>

<script>

  export default {

    created() {
      this.$run("nameOfSaga", { argument })
      .then((res) => {
        // when saga has finished
      })
    }

  }

</script>

The $run method is similiar with store.dispatch method. But it can run the generator function. At the last, it will pass a returned value from the generator function (saga). Do you think it's not comfortable to write? we also have mapSagas() method that will mapping our sagas to the local methods just like the Vuex.mapActions() do.

<template>
  <div>
    <h1>Hello World</h1>
  </div>
</template>

<script>

  import { mapSagas } from 'vuex-saga';

  export default {

    methods: {
      ...mapSagas({
        test: "nameOfSaga"
      })
    },

    created() {
      this.test({ argument })
      .then((res) => {
        // when saga has finished
      })
    }

  }

</script>

How if I want to change the state? Should I use store.commit()?

You can still use store.commit() but it will hard to test. Instead, we have a helper method called put() function. It's just the same with call() function but it's used to run some mutation.

import Vuex from 'vuex'
import Vue from 'vue';
import { call, put, delay } from 'vuex-saga'

Vue.use(Vuex)

const store = new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment (state, payload) {
      state.count += payload
    }
  },
  actions: {
    *incrementAsync(store, payload) {
      yield call(delay,1000)
      yield put("increment", 2)

      yield call(delay,700)
      yield put("increment", 10)
      return store.count
    }
  }
})

export default store

How do I test it? Just the same way with the call() testing.

import { call, put, delay } from 'vuex-saga'
import api from '../api'
import { incrementAsync } from '../actions';
import assert from 'assert';

describe('incrementAsync()', function () {
  it('Should Run The Flow Correctly ', function () {
    let process = incrementAsync()

    assert.deepEqual(process.next().value, call(delay,1000))
    assert.deepEqual(process.next().value, put("increment", 2))
    assert.deepEqual(process.next().value, call(delay,700))
    assert.deepEqual(process.next().value, put("increment", 10))
  });

});

So now, you can test the flow and the fetch process separately. It will make your code easy to test. No more reason to not doing a test.

Limitation

There's a limitation. But it wil not make us harder to write. The limitation is, We Can't run nested generator function in parallel

import api from '../api'

function *nestedGenFunc() {
  yield call(delay, 1000)
  return 1000
}

function *fetchFlow() {
  let [a, b] = yield [call(nestedGenFunc), call(nestedGenFunc)] // Will throw error

  // Instead, it will be run
  let [product, other] = yield [call(api.fetch), call(api.fetcOther)]
  let a = yield call(nestedGenFunc) // It will run too
  let b = yield call(nestedGenFunc) // it will run too
}

API

Method Format Deskripsi
*saga() passed arguments *saga(store, payload) It's not a method form vuex-saga. I just want you to know how the saga looks like. It recieve a store object and payload object. We can use it for logic bussiness within our saga. We should notice the (*) star symbol in the function name. It indicate that our function is a generator function
delay() delay(number) It's just a simple method to delay some function inside the saga. Maybe, It will not used cause I made it just for making a fake async proccess
call() call(func, obj) It's used to call some function. For best practice you should wrap your function to be a promise. The second arguments is single object—cause vuex action has only one argument for data payload—which will passed to the our sagas, You can access it from the saga.
put() put(string, obj) It's Used to bind some vuex mutation. The behaviour is same with store.commit() method. The first Argument is the mutation name, and the second is the data payload which will be passed to the mutation
vm.$run() vm.$run(string, obj) It's a method to run the sagas. the behaviour is similiar with store.dispatch() method. The first argument is action name, and the second argument is data payload. This method only run in the component instance. It always return a promise in the end of saga process
mapSagas() mapSagas(obj) It's a method to mapping the sagas to be a local methods of component. the behaviour is similiar with Vuex.mapActions() method. It will return an object. You can check the example above about how to use it or you can check the Vuex.mapAction() method documentation

Credits

Thank You for Making this useful~

Let's talk about some projects with me

Just Contact Me At:

License

MIT Copyright (c) Naufal Rabbani

More Repositories

1

vue2-loading-bar

Simplest Youtube Like Loading Bar Component For Vue 2. http://bosnaufal.github.io/vue2-loading-bar/
JavaScript
269
star
2

vue-mini-shop

Mini Online Shop Built With Vue JS
JavaScript
267
star
3

vue2-scrollbar

The Simplest Pretty Scroll Area Component with custom scrollbar for Vue 2. https://bosnaufal.github.io/vue2-scrollbar
Vue
235
star
4

vue2-autocomplete

Vue 2 Component to make Autocomplete element.
Vue
234
star
5

vue-autocomplete

Autocomplete Component for Vue.Js
JavaScript
207
star
6

vue-simple-pwa

Simple Progressive Web App Built with Vue Js. https://bosnaufal.github.io/vue-simple-pwa
CSS
196
star
7

react-file-base64

React Component for Converting File to base64
JavaScript
158
star
8

vue-loading-bar

Youtube Like Loading Bar Component for Vue.js
JavaScript
139
star
9

vue-scrollbar

The Simplest Scroll Area Component with custom scrollbar for Vue Js. https://bosnaufal.github.io/vue-scrollbar/
Vue
118
star
10

react-simple-pwa

Simple Progressive Web App Built with React Js. https://bosnaufal.github.io/react-simple-pwa
JavaScript
112
star
11

vue-freeze

Simple state management whitout bloating API and Concept for Vue.js.
JavaScript
74
star
12

react-scrollbar

The Simplest Scroll Area Component with custom scrollbar for React JS. https://bosnaufal.github.io/react-scrollbar
JavaScript
71
star
13

vue-simple-store

Store Organizer To Simplify Your Stores
JavaScript
67
star
14

vue-image-compressor

Vue Component To Compress Image Files Via Client Side. https://bosnaufal.github.io/vue-image-compressor
Vue
56
star
15

vue-file-base64

Vue Component for Convert File to base64
Vue
49
star
16

javascript-sabuk-putih

Modal Awal Belajar Fundamental Javascript Untuk Mendalami React JS, Vue JS, Angular JS, ataupun Node JS
HTML
49
star
17

vue-ripple

Vue Component to Make Google Material Design Ripple Effect. http://bosnaufal.github.io/vue-ripple/
Vue
48
star
18

terbilang-js

Convert Number Into Indonesian Words By Frontend's way
JavaScript
45
star
19

vue-click-outside

Vue Directive to make a click outside event
JavaScript
41
star
20

react-loading-bar

Simplest Youtube Like Loading Bar Component For React Js. http://bosnaufal.github.io/react-loading-bar/
JavaScript
40
star
21

vue-starter

Simple Vue Js Starter for single page application with Vuex and Vue Router
JavaScript
36
star
22

react-ripple

React Component to Make Google Material Design Ripple Effect. http://bosnaufal.github.io/react-ripple/
HTML
33
star
23

vue-testing

Let's make Vue Testing And Mocking Become Easier And Much Fun
JavaScript
31
star
24

secure-local-storage

Javascript Library to make a secure local storage with encryption
JavaScript
28
star
25

click-outside-js

Standalone javascript library to make click outside event. https://bosnaufal.github.io/click-outside-js/
HTML
18
star
26

react-image-compressor

React Component To Compress Image Files Via Client Side. https://bosnaufal.github.io/react-image-compressor
JavaScript
18
star
27

react-starter

Simple starter for React Js project. Completely built with simple configuration
JavaScript
16
star
28

css-itu-mudah

Mini ebook untuk belajar CSS mulai dari dasar
Vue
14
star
29

vue2-starter

Simple Vue 2 Starter for single page application with Vuex and Vue Router
JavaScript
11
star
30

vue-move-dom

Vue Directive to move the DOM without losing all the VM data, event, etc. it's Adopted from https://github.com/rhyzx/vue-transfer-dom
JavaScript
7
star
31

counting-day

Mini library to count your day
JavaScript
6
star
32

javascript-for-all

Javascript Untuk Pemula, Javascript Untuk Semua: Basic Examples
JavaScript
5
star
33

micro-blogging-vue

Micro Blogging System Built with Vue.js
JavaScript
5
star
34

terbilang-vue

Vue Filter to Convert Number into Indonesian words.
5
star
35

fake-api

Javascript library to make Fake API like a Real API. YOU DON'T NEED SERVER!
JavaScript
4
star
36

vue-calc-input

Vue directive to make a calculator input behavior. Implementation of https://github.com/BosNaufal/readable-number
JavaScript
4
star
37

mage-its-pwa

Boilerplate dan Hasil Jadi untuk Workshop Mage ITS tentang Progressive Web App
JavaScript
4
star
38

readable-number

Simple Javascript function to make an integer become readable and vice versa.
JavaScript
4
star
39

argvalid

Make Sure That Your Variable Is Valid. It's Inspired By https://vuejs.org/v2/guide/components.html#Props
JavaScript
4
star
40

meetups

Kumpulan Kodingan Meet up
JavaScript
3
star
41

virtual-element

Simple Reactive Web Component With Virtual DOM for building user interfaces | https://bosnaufal.github.io/virtual-element
JavaScript
2
star
42

runner-js

Better Async Handler To Simplify Your Async Flow Process And Code Testing
JavaScript
1
star
43

vue-freeze-todomvc

Vue Freeze Todo MVC Example
JavaScript
1
star
44

lazy-image

Standalone Javascript Library to lazy load the image. You can use it without jquery~
JavaScript
1
star
45

vuedova

Tool for developing Cordova with Vue Js
JavaScript
1
star
46

test-checkbox

Simple Checkbox with custom label component
Vue
1
star
47

react-click-outside

Click outside event in React Js
JavaScript
1
star
48

react-calc-input

A React Component to make a calculator input behavior
JavaScript
1
star
49

vue-readable-number

Vue Filter to make an integer become readable and vice versa. The Vue implementation of https://github.com/BosNaufal/readable-number
JavaScript
1
star
50

vue-boilerplate

Vue Boilerplate inspired by https://github.com/mxstbr/react-boilerplate
JavaScript
1
star
51

cordova-iframe-generator

Base Generator for cordova-iframe
JavaScript
1
star
52

secure-string-js

Simple Javascript Encryptor / Decryptor to make your string or variable become secure
JavaScript
1
star
53

online-shop-starter

Super Simple Online Shop Starter. https://github.com/BosNaufal/vue-mini-shop
CSS
1
star