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 specificationSide effects

2.redux-sagaThe working principle of

  • redux-sageusinggeneratorFunction toyield effects
  • generatorThe pause () function pauses execution and then resumes where it left off
  • effectIs a simple object that contains a number of methodsmiddlewareExplain execution information
  • Can be achieved byeffects apiSuch as:fork.call.take.put.cancelTo create such aseffect

3.redux-sageclassification

  • worker saga Work with saga, such as calling apis, making asynchronous requests, and getting the results of asynchronous encapsulation
  • watcher sagaListen for the sent action when it is receivedactionOr when it knows it’s triggeredworkerPerform a task
  • root sagaImmediately startsagaThe 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

  • atakeIt’s like a process running in the background, based onredux-sagaYou can run more than one application at the same timetask
  • throughforkFunction 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