RSync: Redux async middleware
RSync is an alternative to redux-saga to handle async actions without generator.
It makes handling complicated async flow easier and offers a great readability.
Table of Contents
Demo
For a demo, click here.
Installation
yarn add redux-rsync
Getting Started
1. Apply rsync middlware
redux/index.js
import rootReducer from './reducer'
import rsync from 'redux-rsync'
import { createStore, applyMiddleware } from 'redux'
export const store = createStore(
rootReducer,
applyMiddleware(rsync)
)
2. Decorate actions with async or flow metadata
redux/action.js
import api from '../api'
...
export function requestGetUser (payload) {
return {
type: 'REQUEST_GET_USER',
payload,
meta: {
async: {
effect: () => api.user.show(),
resolve: { type: 'RESOLVE_REQUEST_GET_USER' },
reject: { type: 'REJECT_REQUEST_GET_USER' },
take: 'latest'
}
}
}
}
...
redux/flow.js
import { requestGetUser, requestGetPosts } from './action'
import { loadInitialDataParams } from './prepare'
...
export function loadInitialData (payload) {
return {
type: 'LOAD_INITIAL_DATA',
payload,
meta: {
flow: {
actions: [
[
{
effect: requestGetUser,
break: ({ response }) => !response.data.args.user
},
{
prepare: loadInitialDataParams.requestGetPosts,
effect: requestGetPosts
}
]
],
resolve: { type: 'RESOLVE_LOAD_INITIAL_DATA' },
reject: { type: 'REJECT_LOAD_INITIAL_DATA' },
take: 'every:serial'
}
}
}
}
...
Documentation
RSync works by decorating actions with async
and/or flow
metadata
Async
Example
import api from '../api'
...
export function requestGetUser (payload) {
return {
type: 'REQUEST_GET_USER',
payload,
meta: {
async: {
effect: () => api.user.show(),
resolve: { type: 'RESOLVE_REQUEST_GET_USER' },
reject: { type: 'REJECT_REQUEST_GET_USER' },
take: 'latest'
}
}
}
}
...
Properties
Property | Type | Value | Description |
---|
effect | function | payload => {} | Async side effect to run |
resolve | object | { type: '<ACTION_NAME>' } | Will be dispatched if the effect execution is successful. Payload and effect result/response will be passed to the reducer automatically |
reject | object | { type: '<ACTION_NAME>' } | Will be dispatched if the effect execution is failed. Payload and error will be passed to the reducer automatically |
take | string | every:parallel (default), latest | latest : if an action effect still running when another action with the same type is dispatched, then the previous action will be cancelled
every:parallel : take all dispatched actions |
Flow
Example
import api from '../api'
...
export function requestGetUser (payload) {
return {
type: 'REQUEST_GET_USER',
payload,
meta: {
async: {
effect: () => api.user.show(),
resolve: { type: 'RESOLVE_REQUEST_GET_USER' },
reject: { type: 'REJECT_REQUEST_GET_USER' },
take: 'latest'
}
}
}
}
export function requestGetPosts (payload) {
return {
type: 'REQUEST_GET_POSTS',
payload,
meta: {
async: {
effect: () => api.post.index(),
resolve: { type: 'RESOLVE_REQUEST_GET_POSTS' },
reject: { type: 'REJECT_REQUEST_GET_POSTS' },
take: 'latest'
}
}
}
}
...
import { requestGetUser, requestGetPosts } from './action'
import { loadInitialDataParams } from './prepare'
...
export function loadInitialData (payload) {
return {
type: 'LOAD_INITIAL_DATA',
payload,
meta: {
flow: {
actions: [
{
effect: requestGetUser,
break: ({ response }) => !response.data.args.user
},
{
prepare: loadInitialDataParams.requestGetPosts,
effect: requestGetPosts
},
[
{
prepare: loadInitialDataParams.doFoo,
effect: doFoo
},
{
prepare: loadInitialDataParams.doBar,
effect: doBar
},
]
],
resolve: { type: 'RESOLVE_LOAD_INITIAL_DATA' },
reject: { type: 'REJECT_LOAD_INITIAL_DATA' },
take: 'every:serial'
}
}
}
}
...
Properties
Property | Type | Value | Description |
---|
actions | array[object/array] or | [{ effect: () => {}, ... }, ...}] | Array of actions to run. The action will support these following properties: effect , prepare , break
The actions will be executed in order. To run the actions in parallel, Wrapping the actions inside another another array will do it. (see example above)
effect : function that will return redux action with meta:async property. (see example above)
prepare : function to prepare result/response from previous async action into params for the current action
break : function to evaluate the result/response from the action. return true to break the flow or return false to continue |
resolve | object | { type: '<ACTION_NAME>' } | Will be dispatched if the effect execution is successful. Effect result/response will be passed to the reducer automatically. |
reject | object | { type: '<ACTION_NAME>' } | Will be dispatched if the effect execution is failed. Error will be passed to the reducer automatically. |
take | string | first (default), every:serial , every:parallel | first : will not accept any flow actions with the same type with the one that currently running unti it's done
every:serial : take all dispatched flow actions with the same type , put them in a queue and execute them in serial |
Contributing
We appreciate feedback and contribution to this repo! Before you get started, please see the following:
Support + Feedback
- Use Issues for code-level support
- Use Mail for usage, questions, specific cases
License
MIT