Redux-saga source code analysis
1. Whyredux-saga
Redux-sage is a middleware designed to handle the following side effects (asynchronous tasks). It is a process manager who receives events and may trigger new events to manage complex processes for applications
- Redux-saga is a middleware for Redux, and the purpose of middleware is to provide additional functionality for Redux.
- All operations in reducers are synchronous and pure, that is, reducer are pure functions, pure functions:
A function returns results that depend only on its arguments, and does not have external side effects during execution, that is, output what is passed to it.
- But in real application development, we want to do something asynchronous and not pure, which is called in the functional programming specification
Side effects
2.redux-saga
The working principle of
redux-sage
usinggenerator
Function toyield effects
generator
The pause () function pauses execution and then resumes where it left offeffect
Is a simple object that contains a number of methodsmiddleware
Explain execution information- Can be achieved by
effects api
Such as:fork
.call
.take
.put
.cancel
To create such aseffect
3.redux-sage
classification
worker saga
Work with saga, such as calling apis, making asynchronous requests, and getting the results of asynchronous encapsulationwatcher saga
Listen for the sent action when it is receivedaction
Or when it knows it’s triggeredworker
Perform a taskroot saga
Immediately startsaga
The only entrance to
4. Basic
├ ─ ─ components │ └ ─ ─ Counter. Js ├ ─ ─ index. The js └ ─ ─ store ├ ─ ─ action - types. Js ├ ─ ─ the actions │ └ ─ ─ Counter. Js ├ ─ ─ index. The js ├ ─ ─ Reducers │ ├ ─ ─ counter. Js │ └ ─ ─ index. The js └ ─ ─ sagas └ ─ ─ index, jsCopy the code
4.1. Project dependencies
npm install react-redux redux redux-sage -S
Copy the code
4.2.src/index.js
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import store from './store';
import Counter from './components/Counter'
ReactDOM.render(<Provider store={store}>
<Counter />
</Provider>.document.getElementById('root'));
Copy the code
4.2.components/Counter.js
import React from 'react';
import { connect } from 'react-redux';
import actions from '.. /store/actions/counter'
class Counter extends React.Component {
render() {
return <div>
<p>{this.props.num}</p>
<button onClick={this.props.add}>add</button>
<button onClick={this.props.asyncAdd}>async add</button>
</div>}}const mapStateToProps = state= > state.counter;
export default connect(mapStateToProps, actions)(Counter);
Copy the code
4.3.store/index.js
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducers from './reducers';
import rootSage from './sagas';
// Reference saga middleware
const sagaMiddleware = createSagaMiddleware();
// Apply saga middleware. Once this middleware is used, all subsequent store.dispatch points to the dispatch method provided by sagaMiddleware
const store = applyMiddleware(sagaMiddleware)(createStore)(reducers);
// Let root saga start executing
sagaMiddleware.run(rootSage);
export default store;
Copy the code
4.4.store/action-types.js
const ADD='ADD';
const ASYNC_ADD='ASYNC_ADD';
export {
ADD,
ASYNC_ADD
}
Copy the code
4.5.actions/counter.js
import * as types from '.. /action-types';
const actions = {
add() {
return { type: types.ADD };
},
asyncAdd() {
return { type: types.ASYNC_ADD }; }}export default actions;
Copy the code
4.6.reducers/index.js
import { combineReducers } from 'redux';
import counter from './counter';
export default combineReducers({
counter
})
Copy the code
4.7.reducers/counter.js
import * as types from '.. /action-types';
const initialState = { num: 0 }
function counter(state = initialState, action) {
switch (action.type) {
case types.ADD:
return { num: state.num + 1 };
default:
returnstate; }}export default counter;
Copy the code
4.8.sagas/index.js
import { take, put } from 'redux-saga/effects';
import * as types from '.. /action-types';
function* rootSaga() {
for (let i = 0; i < 3; i++) {
// Wait for someone to send an ASYNC_ADD command to the repository, then continue executing it, or get stuck
//take waits only once
yield take(types.ASYNC_ADD);
// Send an action to the warehouse to call store.dispatch({type: types.add})
yield put({ type: types.ADD }); }}export default rootSaga;
Copy the code
5. Put&take implementation
Story - sage ├ ─ ─ createChannel. Js ├ ─ ─ effectTypes. Js ├ ─ ─ effects. The js ├ ─ ─ index. The js └ ─ ─ runSaga. JsCopy the code
5.1.index.js
import runSaga from './runSaga';
import createChannel from "./createChannel";
/ * * * *@returns Returns a middleware */
function createSagaMiddleware() {
const channel = createChannel();
let boundRunSaga;
function sagaMiddleware(middlewareApi) {//{getState,dispatch}
const { getState, dispatch } = middlewareApi;
// bind this to null and the first argument to runSaga to env={getState, dispatch, channel}
boundRunSaga = runSaga.bind(null, { getState, dispatch, channel })
return function (next) {// The next middleware dispatch method
return function (action) {/ / action
// The original dispatch method is implemented, which directly sends async_add to the repository and then to the reducer
next(action);
/ / execution channel. The put
channel.put(action)
}
}
}
sagaMiddleware.run = saga= > boundRunSaga(saga);
return sagaMiddleware;
}
export default createSagaMiddleware;
Copy the code
5.2.createChannel.js
function createChannel() {
let currentTakers = [];// The current listener
/** * start listening for an action *@param {*} ActionType actionType ASYNC_ADD *@param {*} Taker is the next * /
function take(actionType, taker) {
taker.actionType = actionType;
taker.cancel = () = > {
currentTakers = currentTakers.filter(item= >item ! == taker); } currentTakers.push(taker); }/** * Triggers the execution of functions in the Takers array, but configures the action type *@param {*} Action Action object {type:types.ASYNC_ADD} */
function put(action) {
currentTakers.forEach(taker= > {
if (taker.actionType === action.type) {
//take takes effect once by default
taker.cancel();
taker(action);/ / the next function}})}return { take, put };
}
export default createChannel;
Copy the code
5.3.effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
Copy the code
5.4.effects.js
import * as effecTypes from './effectTypes';
/ * * * *@param {*} actionType
* @returns The return value is a plain object, called the instruction object */
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
Copy the code
5.5.runSaga.js
import * as effectTypes from './effectTypes';
/** * How to execute or start saga *@param {*} evn { getState, dispatch, channel }
* @param {*} Saga can be passed either as a generator or as an iterator */
function runSaga(evn, saga) {
const { getState, dispatch, channel } = evn;
// Get the iterator
const it = saga();
function next(value) {
/** * effect={ type: effecTypes.TAKE, actionType=types.ASYNC_ADD } * effect= { type: effecTypes.PUT, action } */
const { value: effect, done } = it.next(value);
if(! done) {switch (effect.type) {
case effectTypes.TAKE:// Wait for an ASYNC_ADD action to be sent to the warehouse
Channel.put (action) if ASYNC_ADD is dispatched to the repository
// It waits for the action to happen, and if it doesn't, it gets stuck
channel.take(effect.actionType, next);
break;
case effectTypes.PUT://put this effect does not block the current saga execution and executes immediately after dispatch
dispatch(effect.action);
// Execute immediately after dispatching
next();
break
default:
break;
}
}
}
next();
}
export default runSaga;
Copy the code
6. Support outputsiterator
6.1.saga/index.js
import { take, put } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
+ function* add() {+// Send an action to the warehouse to call store.dispatch({type: types.add})
+ yield put({ type: types.ADD }); +}function* rootSaga() {
for (let i = 0; i < 3; i++) {
// Wait for someone to send an ASYNC_ADD command to the repository, then continue executing it, or get stuck
//take waits only once
yield take(types.ASYNC_ADD);
+ yieldadd(); }}Copy the code
6.2.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
function runSaga(evn, saga) {
const { getState, dispatch, channel } = evn;
//saga can be either a generator or an iterator
+ const it = typeof saga === 'function' ? saga() : saga;
function next(value) {
const { value: effect, done } = it.next(value);
if(! done) {// Effect may be an iterator yield add();
+ if (typeof effect[Symbol.iterator] === 'function') {
+ runSaga(evn, effect);
+ next();// Does not block current saga+}else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
next();
break
default:
break;
}
}
}
}
next();
}
export default runSaga;
Copy the code
7. Support takeEvery
- a
take
It’s like a process running in the background, based onredux-saga
You can run more than one application at the same timetask
- through
fork
Function to createtask
7.1.saga/index.js
+ import { take, put, takeEvery } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
function* add() {
// Send an action to the warehouse to call store.dispatch({type: types.add})
yield put({ type: types.ADD });
}
function* rootSaga() {+yield takeEvery(types.ASYNC_ADD, add)
}
export default rootSaga;
Copy the code
7.2.redux-saga/effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
/** Starts a new child process, normally without blocking the current saga */
+ export const FORK = 'FORK';
Copy the code
7.3.redux-saga/effects.js
import * as effecTypes from './effectTypes';
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
/** * execute saga * as a new child process@param {*} saga
* @returns * /
+ export function fork(saga) {+return { type: effecTypes.FORK, saga }; +}/** * Waits for each actionType to be dispatched, and then a separate child calls saga to execute *@param {*} actionType
* @param {*} saga
* @returns * /
+ export function takeEvery(actionType, saga) {+function* takeEveryHelper() {+while (true) {// Write an infinite loop that executes each time
+ yield take(actionType);// Wait for an action type
+ yield fork(saga);// Start a new child process to execute saga+} +} +// start a new subprocess to takeEveryHelper
+ returnfork(takeEveryHelper); +}Copy the code
7.4.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
function runSaga(env, saga) {
const { getState, dispatch, channel } = env;
const it = typeof saga === 'function' ? saga() : saga;
function next(value) {
const { value: effect, done } = it.next(value);
if(! done) {if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();
} else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
next();
break;
+ case effectTypes.FORK:// Start a new child process to execute saga
+ runSaga(env, effect.saga);
+ next();// Does not block current saga
+ break;
default:
break;
}
}
}
}
next();
}
export default runSaga;
Copy the code
Support 8.Promise
8.1.sagas/index.js
import { take, put, takeEvery } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
+ const delay = ms= > new Promise((resolve) = >{+setTimeout(resolve, ms); +})function* add() {+yield delay(2000);
// Send an action to the warehouse to call store.dispatch({type: types.add})
yield put({ type: types.ADD });
}
function* rootSaga() {
yield takeEvery(types.ASYNC_ADD, add)
}
export default rootSaga;
Copy the code
8.2.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
function runSaga(env, saga) {
const { getState, dispatch, channel } = env;
const it = typeof saga === 'function' ? saga() : saga;
function next(value) {
const { value: effect, done } = it.next(value);
if(! done) {if (typeof effect[Symbol.iterator] === 'function') { runSaga(env, effect); next(); +}else if (typeof effect.then === 'function') {/ / support promise
+ effect.then(next);// blocks until the promise succeeds and next automatically goes+}else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
next();
break;
case effectTypes.FORK:
runSaga(env, effect.saga);
next();
break;
default:
break;
}
}
}
}
next();
}
export default runSaga;
Copy the code
9. Supportall
9.1.sagas/index.js
+ import { take, put, takeEvery, call } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
const delay = ms= > new Promise((resolve) = > {
setTimeout(resolve, ms);
})
function* add() {+yield call(delay, 1000);
yield put({ type: types.ADD });
}
function* rootSaga() {
yield takeEvery(types.ASYNC_ADD, add)
}
export default rootSaga;
Copy the code
9.2.redux-saga/effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
/** Starts a new child process, normally without blocking the current saga */
export const FORK = 'FORK';
+ /** Calls a function, which by default returns a Promise */
+ export const CALL='CALL';
Copy the code
9.3.redux-saga/effects.js
import * as effecTypes from './effectTypes';
/ * * * *@param {*} actionType
* @returns The return value is a plain object, called the instruction object */
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
/** * execute saga * as a new child process@param {*} saga
* @returns * /
export function fork(saga) {
return { type: effecTypes.FORK, saga };
}
/** * Waits for each actionType to be dispatched, and then a separate child calls saga to execute *@param {*} actionType
* @param {*} saga
* @returns * /
export function takeEvery(actionType, saga) {
function* takeEveryHelper() {
while (true) {// Write an infinite loop that executes each time
yield take(actionType);// Wait for an action type
yield fork(saga);// Start a new child process to execute saga}}// start a new subprocess to takeEveryHelper
return fork(takeEveryHelper);
}
+ export function call(fn, ... args) {+return { type: effecTypes.CALL, fn, args }
+ }
Copy the code
9.4.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
function runSaga(env, saga) {
const { getState, dispatch, channel } = env;
const it = typeof saga === 'function' ? saga() : saga;
function next(value) {
const { value: effect, done } = it.next(value);
if(! done) {if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();
} else if (typeof effect.then === 'function') {
effect.then(next);
} else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
next();
break;
case effectTypes.FORK:
runSaga(env, effect.saga);
next();
break;
+ caseeffectTypes.CALL: + effect.fn(... effect.args).then(next) +break;
default:
break;
}
}
}
}
next();
}
export default runSaga;
Copy the code
Support 10.cps
10.1.sagas/index.js
import { take, put, takeEvery, call, cps } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
+ const delay2 = (ms, callback) = >{+setTimeout(() = > {
// The first argument is an error result
+ callback(null.'ok') + }, ms); +}function* add() {
// Tell saga middleware to execute delay2 iteration with 1000
+ yield cps(delay2, 1000);
yield put({ type: types.ADD });
}
function* rootSaga() {
yield takeEvery(types.ASYNC_ADD, add)
}
export default rootSaga;
Copy the code
10.2.redux-saga/effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
/** Starts a new child process, normally without blocking the current saga */
export const FORK = 'FORK';
/** Calls a function, which by default returns a Promise */
export const CALL = 'CALL';
/** Call a function whose last argument should be a callback that allows saga to proceed */
+ export const CPS = 'CPS';
Copy the code
10.3.redux-saga/effects.js
import * as effecTypes from './effectTypes';
/ * * * *@param {*} actionType
* @returns The return value is a plain object, called the instruction object */
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
/** * execute saga * as a new child process@param {*} saga
* @returns * /
export function fork(saga) {
return { type: effecTypes.FORK, saga };
}
/** * Waits for each actionType to be dispatched, and then a separate child calls saga to execute *@param {*} actionType
* @param {*} saga
* @returns * /
export function takeEvery(actionType, saga) {
function* takeEveryHelper() {
while (true) {// Write an infinite loop that executes each time
yield take(actionType);// Wait for an action type
yield fork(saga);// Start a new child process to execute saga}}// start a new subprocess to takeEveryHelper
return fork(takeEveryHelper);
}
export function call(fn, ... args) {
return { type: effecTypes.CALL, fn, args }
}
+ export function cps(fn, ... args) {+return { type: effecTypes.CPS, fn, args }; +}Copy the code
10.4.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
function runSaga(env, saga) {
const { getState, dispatch, channel } = env;
const it = typeof saga === 'function' ? saga() : saga;
function next(value, isError) {+let result;
+ if (isError) {
+ result = it.throw(value);// The iterator failed+}else{ + result = it.next(value); +},const { value: effect, done } = result;
if(! done) {// Effect may be an iterator yield add();
if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();// Does not block current saga
} else if (typeof effect.then === 'function') {/ / support promise
effect.then(next);// blocks until the promise succeeds and next automatically goes
} else {
switch (effect.type) {
case effectTypes.TAKE:// Wait for an ASYNC_ADD action to be sent to the warehouse
Channel.put (action) if ASYNC_ADD is dispatched to the repository
// It waits for the action to happen, and if it doesn't, it gets stuck
channel.take(effect.actionType, next);
break;
case effectTypes.PUT://put this effect does not block the current saga execution and executes immediately after dispatch
dispatch(effect.action);
// Execute immediately after dispatching
next();
break;
case effectTypes.FORK:// Start a new child process to execute saga
runSaga(env, effect.saga);
next();// Does not block current saga
break;
caseeffectTypes.CALL: effect.fn(... effect.args).then(next)break;
+ caseeffectTypes.CPS: + effect.fn(... effect.args,(err, data) = >{+if (err) {// If err is not null, there is an error. The first argument to next is an error object
+ next(err, true); +}else{ + next(data); +} +}) +break;
default:
break;
}
}
}
}
next();
}
export default runSaga;
Copy the code
11. Supportall
11.1.sagas/index.js
+ import { take, put, takeEvery, call, cps, all } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
+ function* add1() {+for (let i = 0; i < 3; i++) {
+ yield take(types.ASYNC_ADD);
+ yield put({ type: types.ADD })
+ }
+ return 'add1'; +},function* add2() {+for (let i = 0; i < 2; i++) {
+ yield take(types.ASYNC_ADD);
+ yield put({ type: types.ADD })
+ }
+ return 'add2'; +}function* rootSaga() {+yield all([add1, add2]);
+ console.log(result)//=>['add1','add2']
}
export default rootSaga;
Copy the code
11.2.redux-saga/effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
/** Starts a new child process, normally without blocking the current saga */
export const FORK = 'FORK';
/** Calls a function, which by default returns a Promise */
export const CALL = 'CALL';
/** Call a function whose last argument should be a callback that allows saga to proceed */
export const CPS = 'CPS';
+ /** Receive multiple iterators until all iterators are finished */
+ export const ALL = 'ALL';
Copy the code
11.3.redux-saga/effects.js
import * as effecTypes from './effectTypes';
/ * * * *@param {*} actionType
* @returns The return value is a plain object, called the instruction object */
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
/** * execute saga * as a new child process@param {*} saga
* @returns * /
export function fork(saga) {
return { type: effecTypes.FORK, saga };
}
/** * Waits for each actionType to be dispatched, and then a separate child calls saga to execute *@param {*} actionType
* @param {*} saga
* @returns * /
export function takeEvery(actionType, saga) {
function* takeEveryHelper() {
while (true) {// Write an infinite loop that executes each time
yield take(actionType);// Wait for an action type
yield fork(saga);// Start a new child process to execute saga}}// start a new subprocess to takeEveryHelper
return fork(takeEveryHelper);
}
export function call(fn, ... args) {
return { type: effecTypes.CALL, fn, args }
}
export function cps(fn, ... args) {
return { type: effecTypes.CPS, fn, args };
}
+ export function all(effects) {+return { type: effecTypes.ALL, effects }; +}Copy the code
11.4.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
+ function runSaga(env, saga, callbackDone) {
const { getState, dispatch, channel } = env;
//saga can be either a generator or an iterator
const it = typeof saga === 'function' ? saga() : saga;
function next(value, isError) {
let result;
if (isError) {
result = it.throw(value);// The iterator failed
} else {
result = it.next(value);
}
const { value: effect, done } = result;
if(! done) {// Effect may be an iterator yield add();
if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();// Does not block current saga
} else if (typeof effect.then === 'function') {/ / support promise
effect.then(next);// blocks until the promise succeeds and next automatically goes
} else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
// Execute immediately after dispatching
next();
break;
case effectTypes.FORK:// Start a new child process to execute saga
runSaga(env, effect.saga);
next();// Does not block current saga
break;
caseeffectTypes.CALL: effect.fn(... effect.args).then(next)break;
caseeffectTypes.CPS: effect.fn(... effect.args,(err, data) = > {
if (err) {// If err is not null, there is an error. The first argument to next is an error object
next(err, true);
} else{ next(data); }})break;
+ case effectTypes.ALL:
+ let effects = effect.effects;
+ const result = [];
+ let completedCount = 0;
+ effects.forEach((effect, index) = > {
+ runSaga(env, effect, (res) = > {
+ result[index] = res;
+ // Determine whether the number of completions is equal to the total number
+ if (++completedCount === effects.length) {
+ next(result);// The current saga can continue+} +}) +}) +break;
default:
break; +}}}else {
+ callbackDone && callbackDone(effect);
+ }
}
next();
}
export default runSaga;
Copy the code
Support 12.cancel
12.1.components/Counter.js
import React from 'react';
import { connect } from 'react-redux';
import actions from '.. /store/actions/counter'
class Counter extends React.Component {
render() {
return <div>
<p>{this.props.num}</p>
<button onClick={this.props.add}>add</button>
<button onClick={this.props.asyncAdd}>async add</button>
+ <button onClick={this.props.stop}>stop</button>
</div>}}const mapStateToProps = state= > state.counter;
export default connect(mapStateToProps, actions)(Counter);
Copy the code
12.2.store/action-types.js
const ADD='ADD';
const ASYNC_ADD='ASYNC_ADD';
+ const STOP_ADD='STOP_ADD';
export {
ADD,
ASYNC_ADD,
+ STOP_ADD
}
Copy the code
12.3.actions/counter.js
import * as types from '.. /action-types';
const actions = {
add() {
return { type: types.ADD };
},
asyncAdd() {
return { type: types.ASYNC_ADD };
},
+ stop(){+return { type: types.STOP_ADD }; +}}export default actions;
Copy the code
12.4.sagas/index.js
+ import { take, put, takeEvery, call, cps, all, delay, fork, cancel } from '.. /.. /redux-saga/effects';
import * as types from '.. /action-types';
+ function* add() {+while (true) {+yield delay(1000);
+ yield put({ type: types.ADD }); + +}}function* rootSaga() {+const task = yield fork(add);
+ yield take(types.STOP_ADD);
+ yield cancel(task);
}
export default rootSaga;
Copy the code
12.5.redux-saga/effectTypes.js
/** listen for specific actions */
export const TAKE = 'TAKE';
/** Send actions to the warehouse */
export const PUT = 'PUT';
/** Starts a new child process, normally without blocking the current saga */
export const FORK = 'FORK';
/** Calls a function, which by default returns a Promise */
export const CALL = 'CALL';
/** Call a function whose last argument should be a callback that allows saga to proceed */
export const CPS = 'CPS';
/** Receive multiple iterators until all iterators are finished */
export const ALL = 'ALL';
+ /** Cancel a task */
+ export const CANCEL='CANCEL';
Copy the code
12.6.redux-saga/
import * as effecTypes from './effectTypes';
/ * * * *@param {*} actionType
* @returns The return value is a plain object, called the instruction object */
export function take(actionType) {
return { type: effecTypes.TAKE, actionType };
}
export function put(action) {
return { type: effecTypes.PUT, action }
}
/** * execute saga * as a new child process@param {*} saga
* @returns * /
export function fork(saga) {
return { type: effecTypes.FORK, saga };
}
/** * Waits for each actionType to be dispatched, and then a separate child calls saga to execute *@param {*} actionType
* @param {*} saga
* @returns * /
export function takeEvery(actionType, saga) {
function* takeEveryHelper() {
while (true) {// Write an infinite loop that executes each time
yield take(actionType);// Wait for an action type
yield fork(saga);// Start a new child process to execute saga}}// start a new subprocess to takeEveryHelper
return fork(takeEveryHelper);
}
export function call(fn, ... args) {
return { type: effecTypes.CALL, fn, args }
}
export function cps(fn, ... args) {
return { type: effecTypes.CPS, fn, args };
}
export function all(effects) {
return { type: effecTypes.ALL, effects };
}
+ export function cancel(task) {+return { type: effecTypes.CANCEL, task }; +},function delayP(ms) {+const promise = new Promise(resolve= >{+setTimeout(resolve, ms); +}); +returnpromise; +},export const delay = call.bind(null, delayP);
Copy the code
12.7.redux-saga/symbols.js
export const TASK_CANCEL=Symbol('TASK_CANCEL');
Copy the code
12.8.redux-saga/runSaga.js
import * as effectTypes from './effectTypes';
+ import { TASK_CANCEL } from './symbols';
function runSaga(env, saga, callbackDone) {
// Every time runSaga is executed, a task object is created for it
+ const task = { cancel: () = > next(TASK_CANCEL) };
const { getState, dispatch, channel } = env;
const it = typeof saga === 'function' ? saga() : saga;
function next(value, isError) {
let result;
if(isError) { result = it.throw(value); +}else if (value === TASK_CANCEL) {// If next=TASK_CANCEL, we cancel the current task
+ result = it.return(value);//it.return() to end the current saga+}else {
result = it.next(value);
}
const { value: effect, done } = result;
if(! done) {if (typeof effect[Symbol.iterator] === 'function') {
runSaga(env, effect);
next();
} else if (typeof effect.then === 'function') {
effect.then(next);
} else {
switch (effect.type) {
case effectTypes.TAKE:
channel.take(effect.actionType, next);
break;
case effectTypes.PUT:
dispatch(effect.action);
// Execute immediately after dispatching
next();
break;
case effectTypes.FORK:// Start a new child process to execute saga
+ const forkTask = runSaga(env, effect.saga);Return a task
+ next(forkTask);// Does not block current saga
break;
caseeffectTypes.CALL: effect.fn(... effect.args).then(next)break;
caseeffectTypes.CPS: effect.fn(... effect.args,(err, data) = > {
if (err) {// If err is not null, there is an error. The first argument to next is an error object
next(err, true);
} else{ next(data); }})break;
case effectTypes.ALL:
let effects = effect.effects;
const result = [];
let completedCount = 0;
effects.forEach((effect, index) = > {
runSaga(env, effect, (res) = > {
result[index] = res;
// Determine whether the number of completions is equal to the total number
if (++completedCount === effects.length) {
next(result);// The current saga can continue}})})break;
+ case effectTypes.CANCEL:
+ effect.task.cancel();// Call task's cancel method ->next(TASK_CANCEL) to cancel the task
+ next();// The current saga continues without blocking
+ break;
default:
break; }}}else {
callbackDone && callbackDone(effect);
}
}
next();
+ return task;
}
export default runSaga;
Copy the code