This first post explores redux side effects and Dispatch’s Promiseify source code analysis
Reproduced without permission
We know that Redux’s role is to manage application state so that complex applications can better handle data and map views to data. So how does Redux work? Roughly speaking, it means that user actions generate actions, Dispatch receives actions, and new states are generated after reducer processing. Finally, store and view are updated. See 👇 figure
Side effects
Redux is designed with no side effects, and the state of the data changes can be traced back, so redux is full of pure functions.
What about the side effects? Redux doesn’t offer a straightforward solution. However, it provides a middleware mechanism for users to develop middleware for side effect handling. Many excellent middleware also emerged, such as Redux-Thunk, Redux-Promise, redux-Saga, etc. So how do they deal with side effects? Please see 👇
redux-thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) = > next => action= > {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
Copy the code
The most important idea of Redux-Thunk is the Action Creator that accepts a return function. If this Action Creator returns a function, execute it; if not, follow the original next(Action). Because this Action Creator can return a function, you can perform some asynchronous operations in this function.
redux-promise
// Determine if this is a Promise function
import isPromise from 'is-promise';
// Standard Flux action
import { isFSA } from 'flux-standard-action';
export default function promiseMiddleware({ dispatch }) {
return next= > action => {
// First check whether the action is the type specified by Flux
if(! isFSA(action)) {return isPromise(action) ? action.then(dispatch) : next(action);
}
return isPromise(action.payload)
? action.payload
.then(result= >dispatch({ ... action,payload: result }))
.catch(error= >{ dispatch({ ... action,payload: error, error: true });
return Promise.reject(error);
})
: next(action);
};
}
Copy the code
Redux-promise Follow through on your promise. Passing the promise as an action to Dispatch and letting the middleware handle resolve eliminates the need to write.then().catch() code. Redux-thunk and redux-Promise are actually quite similar in that they both trigger a function/promise and let the middleware decide when to handle the side effects. This solves most side effects scenarios, but for more complex side effects, a lot of code is required. Redux-saga is a good solution to this problem.
redux-saga
Usage and code will not be discussed. Talking about the concept, Redux-Saga has created a Saga layer specifically designed to handle side effects. So how does Redux-Saga deal with side effects? First, the user behavior generates actions, and when reducer is dispatched, the saga layer listens to specific actions (Redux-Saga provides some auxiliary functions to listen to the actions to be sent to reducer) for processing. The function that handles action is called Effect. Effect is a Generate function that provides yield to control code execution, so redux-saga is suitable for handling asynchracy. In addition, a normal action can continue to be initiated in Effect and be processed by reducer. This is the general implementation of Redux-Saga. Okay, now to the second topic of this article, how to dispatch Promiseify.
dispatch promiseify
Dispatch Promiseify here is implemented using Redux and Redux-Saga. So here we go.
Objective to realize
function* asyncIncrease(action) {
return action.payload
}
// Omit some steps. store.dispatch({type: 'asyncIncrease'.payload: 30
}).then(res= > {
console.log(res); / / 30
})
Copy the code
Since Effect is a function Generate, let’s take a look at some concepts of Generate.
- Formally,
Generator
A function is an ordinary function, but it has two characteristics. One is that,function
There is an asterisk between the keyword and the function name; The second is internal use of the function bodyyield
Expression that defines different internal states - call
Generator
The function does not execute and returns not the result of the function’s execution, but a pointer object to the internal state - encounter
yield
Expression, suspends subsequent operations and will immediately followyield
The value of the following expression as the value of the returned objectvalue
Attribute values.
Okay, that’s it for now, and let’s see how it works. The user initiated the action and sent it to the reducer. Because Reducer is a pure function, that is, the same input always gets the same output without any observable side effects. Therefore, we need to distinguish between actions handled by Redux-saga or reducer. So you have middleware, and you return a promise that enables Dispatch to use the THEN method.
const promiseMiddlware = (a)= > next => action= > {
// Use type to determine if it is handled by Effect
// If so, return promise
if (isEffect(action.type) {
return new Promise((resolve, reject) = >{ next({ resolve, reject, ... action, }) }) }else {
return next(action)
}
}
Copy the code
// Action contains resolve, reject, and payload
function* asyncIncreate(action) {
const { resolve, payload } = action
resolve(payload)
}
store.dispatch({
type: 'asyncIncrease'.payload: 30
}).then(res= > {
console.log(res); / / 30
})
Copy the code
Implement Promiseify at Dispatch, but don’t you think you need to write resolve every time you write Effect? Then let’s change our thinking. Can we have another Effect specifically to wrap the actual Effect, and the outer Effect to resolve?
// 1. Write the outer Effect first
function* baseEffect(action) {
const{resolve, reject,... rest} = action// 2. Execute the actual Effect by yield
const res = yield asyncIncreate(rest)
resolve(res)
}
// asyncIncreate returns the value ok
function* asyncIncreate(action) {
return action.payload
}
// omit steps such as createStore
store.dispatch({
type: 'asyncIncrease'.payload: 30
}).then(res= > {
console.log(res); / / 30
})
Copy the code
Promiseify at Dispatch is now implemented as a promiseify at Dispatch. This is a rough version of the promiseify at Dispatch, but it can be used in a more flexible way. For more information, go to the DVA source code.
conclusion
Redux is the most popular react state management library. Side effects are a problem due to Redux’s design philosophy, but they are not difficult to solve, and there are good middleware to solve them. If you want to dispatch promiseify with Redux and Redux-Saga, you can write middleware with an outer Effect.