redux-saga源码分析

526 阅读12分钟

Redux-saga源码分析

1.为什么需要redux-saga

redux-sage就是用来处理下述副作用(异步任务)的一个中间件。它是一个接收事件,并可能触发新事件的过程管理者,为应用管理复杂的流程

  • redux-saga 是一个 redux 的中间件,而中间件的作用是为 redux 提供额外的功能。
  • 在reducers中的所有操作都是同步的并且是纯粹的,即reducer都是纯函数,纯函数:一个函数的返回结果只依赖于它的参数,并且在执行过程中不会对外部产生副作用,即给它传什么,就输出什么。
  • 但是在实际的应用开发中,我们希望做一些异步且不纯粹的操作,这些函数式编程规范中称为副作用

2.redux-saga工作原理

  • redux-sage采用generator函数来yield effects
  • generator函数的作用是可以暂停执行,再次执行的时候从上次暂停的地方继续执行
  • effect是一个简单的对象,该对象包含了一些给middleware解释执行的信息
  • 可以通过effects api如:fork,call,take,put,cancel等来创建effect

3.redux-sage分类

  • worker saga 工作saga,如调用API,进行异步请求,获取异步封装结果
  • watcher saga 监听被派发的动作,当接受到action或者知道其被触发时,调用worker执行任务
  • root saga立即启动saga的唯一入口

4.基本使用

├── components
│   └── Counter.js
├── index.js
└── store
    ├── action-types.js
    ├── actions
    │   └── counter.js
    ├── index.js
    ├── reducers
    │   ├── counter.js
    │   └── index.js
    └── sagas
        └── index.js

4.1.项目依赖

npm install react-redux redux redux-sage -S

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'));

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);

4.3.store/index.js

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducers from './reducers';
import rootSage from './sagas';

//引用saga中间件
const sagaMiddleware = createSagaMiddleware();
//应用saga中间件,一旦使用了这个中间件,那么之后的store.dispatch都会指向sagaMiddleware提供的dispatch方法
const store = applyMiddleware(sagaMiddleware)(createStore)(reducers);
//让根saga开始启动执行
sagaMiddleware.run(rootSage);
export default store;

4.4.store/action-types.js

const ADD='ADD';
const ASYNC_ADD='ASYNC_ADD';
export {
    ADD,
    ASYNC_ADD
}

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;

4.6.reducers/index.js

import { combineReducers } from 'redux';
import counter from './counter';

export default combineReducers({
    counter
})

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:
            return state;
    }
}
export default counter;

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++) {
      	//等待有人向仓库派发一个ASYNC_ADD这样的命令,等到了就会继续执行,等不到就卡在这里
      	//take只等待一次
        yield take(types.ASYNC_ADD);
      	//向仓库派发一个动作,让仓库调用store.dispatch({type:types.ADD})
        yield put({ type: types.ADD });
    }
}

export default rootSaga;

5.put&take实现

redux-sage
├── createChannel.js
├── effectTypes.js
├── effects.js
├── index.js
└── runSaga.js

5.1.index.js

import runSaga from './runSaga';
import createChannel from "./createChannel";

/**
 * 
 * @returns 返回的是一个中间件
 */
function createSagaMiddleware() {
    const channel = createChannel();
    let boundRunSaga;
    function sagaMiddleware(middlewareApi) {//{getState,dispatch}
        const { getState, dispatch } = middlewareApi;
        //把this绑定为null,把runSaga的第一个参数绑定为env={ getState, dispatch, channel }
        boundRunSaga = runSaga.bind(null, { getState, dispatch, channel })
        return function (next) {//下一次中间件的dispatch方法
            return function (action) {//动作
                //原生的dispatch方法执行,直接把async_add给仓库,又给reducer,reducer不能识别,什么也不做
                next(action);
                //执行channel.put
                channel.put(action)
            }
        }
    }
    sagaMiddleware.run = saga => boundRunSaga(saga);
    return sagaMiddleware;
}
export default createSagaMiddleware;

5.2.createChannel.js

function createChannel() {
    let currentTakers = [];//当前的监听者

    /**
     * 开始监听某个动作
     * @param {*} actionType 动作类型 ASYNC_ADD
     * @param {*} taker 是next
     */
    function take(actionType, taker) {
        taker.actionType = actionType;
        taker.cancel = () => {
            currentTakers = currentTakers.filter(item => item !== taker);
        }
        currentTakers.push(taker);
    }

    /**
     * 触发takers数组中的函数执行,但是要配置动作类型
     * @param {*} action 动作对象 {type:types.ASYNC_ADD}
     */
    function put(action) {
        currentTakers.forEach(taker => {
            if (taker.actionType === action.type) {
                //take默认执行一次,需要取消掉
                taker.cancel();
                taker(action);//next函数
            }
        })
    }

    return { take, put };
}
export default createChannel;

5.3.effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

5.4.effects.js

import * as effecTypes from './effectTypes';

/**
 * 
 * @param {*} actionType 
 * @returns 返回值是一个普通对象,称之为指令对象
 */
export function take(actionType) {
    return { type: effecTypes.TAKE, actionType };
}

export function put(action) {
    return { type: effecTypes.PUT, action }
}

5.5.runSaga.js

import * as effectTypes from './effectTypes';
/**
 * 执行或者说启动saga的方法
 * @param {*} evn { getState, dispatch, channel }
 * @param {*} saga 可以传过来的是一个生成器,也可能是一个迭代器
 */
function runSaga(evn, saga) {
    const { getState, dispatch, channel } = evn;
    //获取迭代器
    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://等待有人向仓库派发ASYNC_ADD类型的动作
                    //如果有人向仓库派发了ASYNC_ADD动作,就执行channel.put(action)
                    //它会等待动作发生,如果等不到,就卡在这里
                    channel.take(effect.actionType, next);
                    break;
                case effectTypes.PUT://put这个effect不会阻塞当前的saga执行,派发完成后,立即向下执行
                    dispatch(effect.action);
                    //派发完成后,立即向下执行
                    next();
                    break
                default:
                    break;
            }
        }
    }
    next();
}
export default runSaga;

6.支持产出iterator

6.1.saga/index.js

import { take, put } from '../../redux-saga/effects';
import * as types from '../action-types';

+ function* add() {
+   //向仓库派发一个动作,让仓库调用store.dispatch({type:types.ADD})
+   yield put({ type: types.ADD });
+ }

function* rootSaga() {
  for (let i = 0; i < 3; i++) {
    //等待有人向仓库派发一个ASYNC_ADD这样的命令,等到了就会继续执行,等不到就卡在这里
    //take只等待一次
    yield take(types.ASYNC_ADD);
+    yield add();
  }
}

6.2.redux-saga/runSaga.js

import * as effectTypes from './effectTypes';

function runSaga(evn, saga) {
    const { getState, dispatch, channel } = evn;
    //saga可能是生成器,也可能是迭代器
+   const it = typeof saga === 'function' ? saga() : saga;
    function next(value) {
        const { value: effect, done } = it.next(value);
        if (!done) {
            //effect可能是一个迭代器 yield add();
+           if (typeof effect[Symbol.iterator] === 'function') {
+               runSaga(evn, effect);
+               next();//不会阻塞当前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;

7.支持takeEvery

  • 一个take就像是一个在后台运行的进程,在基于redux-saga的应用程序中,可以i同时运行多个task
  • 通过fork函数来创建task

7.1.saga/index.js

+ import { take, put, takeEvery } from '../../redux-saga/effects';
  import * as types from '../action-types';

function* add() {
  //向仓库派发一个动作,让仓库调用store.dispatch({type:types.ADD})
  yield put({ type: types.ADD });
}

function* rootSaga() {
+  yield takeEvery(types.ASYNC_ADD, add)
}

export default rootSaga;

7.2.redux-saga/effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

  /**开启一个新的子进程,一般不会阻塞当前saga */
+ export const FORK = 'FORK';

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 }
}

/**
 * 以新的子进程的方式执行saga
 * @param {*} saga 
 * @returns 
 */
+ export function fork(saga) {
+    return { type: effecTypes.FORK, saga };
+ }

/**
 * 等待每一次的actionType派发,然后一单独的子进程调用saga执行
 * @param {*} actionType 
 * @param {*} saga 
 * @returns 
 */
+ export function takeEvery(actionType, saga) {
+    function* takeEveryHelper() {
+       while (true) {//写一个死循环,每次都执行
+           yield take(actionType);//等待一个动作类型
+           yield fork(saga);//开启一个新的子进程执行saga
+       }
+   }
+   //开一个新的子进程执行 takeEveryHelper这个saga
+   return fork(takeEveryHelper);
+ }

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://开启一个新的子进程去执行saga
+                       runSaga(env, effect.saga);
+                       next();//不会阻塞当前saga
+                       break;
                    default:
                        break;
                }
            }
        }
    }
    next();
}
export default runSaga;

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);
  //向仓库派发一个动作,让仓库调用store.dispatch({type:types.ADD})
  yield put({ type: types.ADD });
}

function* rootSaga() {
  yield takeEvery(types.ASYNC_ADD, add)
}

export default rootSaga;

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') {//支持promise
+               effect.then(next);//会阻塞,直到promise成功后会自动走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;
                    default:
                        break;
                }
            }
        }
    }
    next();
}
export default runSaga;

9.支持all

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;

9.2.redux-saga/effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

/**开启一个新的子进程,一般不会阻塞当前saga */
export const FORK = 'FORK';

+ /**调用一个函数,默认此函数需要返回一个Promise */
+ export const CALL='CALL';

9.3.redux-saga/effects.js

import * as effecTypes from './effectTypes';

/**
 * 
 * @param {*} actionType 
 * @returns 返回值是一个普通对象,称之为指令对象
 */
export function take(actionType) {
    return { type: effecTypes.TAKE, actionType };
}

export function put(action) {
    return { type: effecTypes.PUT, action }
}

/**
 * 以新的子进程的方式执行saga
 * @param {*} saga 
 * @returns 
 */
export function fork(saga) {
    return { type: effecTypes.FORK, saga };
}

/**
 * 等待每一次的actionType派发,然后一单独的子进程调用saga执行
 * @param {*} actionType 
 * @param {*} saga 
 * @returns 
 */
export function takeEvery(actionType, saga) {
    function* takeEveryHelper() {
        while (true) {//写一个死循环,每次都执行
            yield take(actionType);//等待一个动作类型
            yield fork(saga);//开启一个新的子进程执行saga
        }
    }
    //开一个新的子进程执行 takeEveryHelper这个saga
    return fork(takeEveryHelper);
}


+ export function call(fn, ...args) {
+    return { type: effecTypes.CALL, fn, args }
+ }

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;
+                   case effectTypes.CALL:
+                       effect.fn(...effect.args).then(next)
+                       break;
                    default:
                        break;
                }
            }
        }
    }
    next();
}
export default runSaga;

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(() => {
    //第一个参数是错误结果
+    callback(null,'ok')
+ }, ms);
+ }

function* add() {
  //告诉saga中间件执行delay2反复,参数是1000
+  yield cps(delay2, 1000);
  
  yield put({ type: types.ADD });
}

function* rootSaga() {
  yield takeEvery(types.ASYNC_ADD, add)
}

export default rootSaga;

10.2.redux-saga/effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

/**开启一个新的子进程,一般不会阻塞当前saga */
export const FORK = 'FORK';

/**调用一个函数,默认此函数需要返回一个Promise */
export const CALL = 'CALL';

	/**调用一个函数,此函数的最后一个参数应该是一个callback,调用callback可以让saga继续向下执行 */
+ export const CPS = 'CPS';

10.3.redux-saga/effects.js

import * as effecTypes from './effectTypes';

/**
 * 
 * @param {*} actionType 
 * @returns 返回值是一个普通对象,称之为指令对象
 */
export function take(actionType) {
    return { type: effecTypes.TAKE, actionType };
}

export function put(action) {
    return { type: effecTypes.PUT, action }
}

/**
 * 以新的子进程的方式执行saga
 * @param {*} saga 
 * @returns 
 */
export function fork(saga) {
    return { type: effecTypes.FORK, saga };
}

/**
 * 等待每一次的actionType派发,然后一单独的子进程调用saga执行
 * @param {*} actionType 
 * @param {*} saga 
 * @returns 
 */
export function takeEvery(actionType, saga) {
    function* takeEveryHelper() {
        while (true) {//写一个死循环,每次都执行
            yield take(actionType);//等待一个动作类型
            yield fork(saga);//开启一个新的子进程执行saga
        }
    }
    //开一个新的子进程执行 takeEveryHelper这个saga
    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 };
+ }

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);//迭代器出错了
+       } else {
+           result = it.next(value);
+       }
+       const { value: effect, done } = result;
        if (!done) {
            //effect可能是一个迭代器 yield add();
            if (typeof effect[Symbol.iterator] === 'function') {
                runSaga(env, effect);
                next();//不会阻塞当前saga
            } else if (typeof effect.then === 'function') {//支持promise
                effect.then(next);//会阻塞,直到promise成功后会自动走next
            } else {
                switch (effect.type) {
                    case effectTypes.TAKE://等待有人向仓库派发ASYNC_ADD类型的动作
                        //如果有人向仓库派发了ASYNC_ADD动作,就执行channel.put(action)
                        //它会等待动作发生,如果等不到,就卡在这里
                        channel.take(effect.actionType, next);
                        break;
                    case effectTypes.PUT://put这个effect不会阻塞当前的saga执行,派发完成后,立即向下执行
                        dispatch(effect.action);
                        //派发完成后,立即向下执行
                        next();
                        break;
                    case effectTypes.FORK://开启一个新的子进程去执行saga
                        runSaga(env, effect.saga);
                        next();//不会阻塞当前saga
                        break;
                    case effectTypes.CALL:
                        effect.fn(...effect.args).then(next)
                        break;
+                   case effectTypes.CPS:
+                       effect.fn(...effect.args, (err, data) => {
+                           if (err) {//如果err不为null,说明错误了,next第一个参数是错误对象
+                               next(err, true);
+                           } else {
+                               next(data);
+                           }
+                       })
+                       break;
                    default:
                        break;
                }
            }
        }
    }
    next();
}
export default runSaga;

11.支持all

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;

11.2.redux-saga/effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

/**开启一个新的子进程,一般不会阻塞当前saga */
export const FORK = 'FORK';

/**调用一个函数,默认此函数需要返回一个Promise */
export const CALL = 'CALL';

/**调用一个函数,此函数的最后一个参数应该是一个callback,调用callback可以让saga继续向下执行 */
export const CPS = 'CPS';

+ /**接收多个iterator,等多个iterator都结束,才会完成结束 */
+ export const ALL = 'ALL';

11.3.redux-saga/effects.js

import * as effecTypes from './effectTypes';

/**
 * 
 * @param {*} actionType 
 * @returns 返回值是一个普通对象,称之为指令对象
 */
export function take(actionType) {
    return { type: effecTypes.TAKE, actionType };
}

export function put(action) {
    return { type: effecTypes.PUT, action }
}

/**
 * 以新的子进程的方式执行saga
 * @param {*} saga 
 * @returns 
 */
export function fork(saga) {
    return { type: effecTypes.FORK, saga };
}

/**
 * 等待每一次的actionType派发,然后一单独的子进程调用saga执行
 * @param {*} actionType 
 * @param {*} saga 
 * @returns 
 */
export function takeEvery(actionType, saga) {
    function* takeEveryHelper() {
        while (true) {//写一个死循环,每次都执行
            yield take(actionType);//等待一个动作类型
            yield fork(saga);//开启一个新的子进程执行saga
        }
    }
    //开一个新的子进程执行 takeEveryHelper这个saga
    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 };
+ }

11.4.redux-saga/runSaga.js

import * as effectTypes from './effectTypes';

+ function runSaga(env, saga, callbackDone) {
    const { getState, dispatch, channel } = env;
    //saga可能是生成器,也可能是迭代器
    const it = typeof saga === 'function' ? saga() : saga;
    function next(value, isError) {
        let result;
        if (isError) {
            result = it.throw(value);//迭代器出错了
        } else {
            result = it.next(value);
        }
        const { value: effect, done } = result;
        if (!done) {
            //effect可能是一个迭代器 yield add();
            if (typeof effect[Symbol.iterator] === 'function') {
                runSaga(env, effect);
                next();//不会阻塞当前saga
            } else if (typeof effect.then === 'function') {//支持promise
                effect.then(next);//会阻塞,直到promise成功后会自动走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://开启一个新的子进程去执行saga
                        runSaga(env, effect.saga);
                        next();//不会阻塞当前saga
                        break;
                    case effectTypes.CALL:
                        effect.fn(...effect.args).then(next)
                        break;
                    case effectTypes.CPS:
                        effect.fn(...effect.args, (err, data) => {
                            if (err) {//如果err不为null,说明错误了,next第一个参数是错误对象
                                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;
+                               //判断完成数量和总的数量是否相等
+                               if (++completedCount === effects.length) {
+                                   next(result);//相等,相当于全部结束了,可以让当前saga继续执行
+                               }
+                           })
+                       })
+                       break;
                    default:
                        break;
                }
            }
+       } else {
+           callbackDone && callbackDone(effect);
+       }
    }
    next();
}
export default runSaga;

12.支持cancel

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);

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
}

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;

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;

12.5.redux-saga/effectTypes.js

/**监听特定的动作 */
export const TAKE = 'TAKE';

/**向仓库派发动作 */
export const PUT = 'PUT';

/**开启一个新的子进程,一般不会阻塞当前saga */
export const FORK = 'FORK';

/**调用一个函数,默认此函数需要返回一个Promise */
export const CALL = 'CALL';

/**调用一个函数,此函数的最后一个参数应该是一个callback,调用callback可以让saga继续向下执行 */
export const CPS = 'CPS';

/**接收多个iterator,等多个iterator都结束,才会完成结束 */
export const ALL = 'ALL';

+ /**取消一个任务 */
+ export const CANCEL='CANCEL';

12.6.redux-saga/

import * as effecTypes from './effectTypes';

/**
 * 
 * @param {*} actionType 
 * @returns 返回值是一个普通对象,称之为指令对象
 */
export function take(actionType) {
    return { type: effecTypes.TAKE, actionType };
}

export function put(action) {
    return { type: effecTypes.PUT, action }
}

/**
 * 以新的子进程的方式执行saga
 * @param {*} saga 
 * @returns 
 */
export function fork(saga) {
    return { type: effecTypes.FORK, saga };
}

/**
 * 等待每一次的actionType派发,然后一单独的子进程调用saga执行
 * @param {*} actionType 
 * @param {*} saga 
 * @returns 
 */
export function takeEvery(actionType, saga) {
    function* takeEveryHelper() {
        while (true) {//写一个死循环,每次都执行
            yield take(actionType);//等待一个动作类型
            yield fork(saga);//开启一个新的子进程执行saga
        }
    }
    //开一个新的子进程执行 takeEveryHelper这个saga
    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);
+    });
+    return promise;
+ }

+ export const delay = call.bind(null, delayP);

12.7.redux-saga/symbols.js

export const TASK_CANCEL=Symbol('TASK_CANCEL');

12.8.redux-saga/runSaga.js

import * as effectTypes from './effectTypes';
+ import { TASK_CANCEL } from './symbols';

function runSaga(env, saga, callbackDone) {
    //每当执行runSaga时,给它创建一个任务对象
+   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) {//如果next=TASK_CANCEL,说明我们取消了当前的任务
+           result = it.return(value);//it.return()用来结束当前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);
                        //派发完成后,立即向下执行
                        next();
                        break;
                    case effectTypes.FORK://开启一个新的子进程去执行saga
+                       const forkTask = runSaga(env, effect.saga);//返回一个task
+                       next(forkTask);//不会阻塞当前saga
                        break;
                    case effectTypes.CALL:
                        effect.fn(...effect.args).then(next)
                        break;
                    case effectTypes.CPS:
                        effect.fn(...effect.args, (err, data) => {
                            if (err) {//如果err不为null,说明错误了,next第一个参数是错误对象
                                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;
                                //判断完成数量和总的数量是否相等
                                if (++completedCount === effects.length) {
                                    next(result);//相等,相当于全部结束了,可以让当前saga继续执行
                                }
                            })
                        })
                        break;
+                   case effectTypes.CANCEL:
+                       effect.task.cancel();//调用task的cancel方法->next(TASK_CANCEL)执行取消任务
+                       next();//当前saga继续执行,不会阻塞
+                       break;
                    default:
                        break;
                }
            }
        } else {
            callbackDone && callbackDone(effect);
        }
    }
    next();
+   return task;
}
export default runSaga;