redux-saga源码解析

栏目: IOS · Android · 发布时间: 7年前

内容简介:Redux-saga是redux应用的又一个副作用模型。可以用来替换redux-thunk中间件。 redux-saga 抽象出我想在分析redux-saga之前,先来看看redux-thunk是怎么一回事 redux 在我之前一篇文章中讲过了链接 那我们就先用原本redux中action只能是

Redux-saga是redux应用的又一个副作用模型。可以用来替换redux-thunk中间件。 redux-saga 抽象出 Effect (影响, 例如等待action、发出action、fetch数据等等),便于组合与测试。

我想在分析redux-saga之前,先来看看redux-thunk是怎么一回事 redux 在我之前一篇文章中讲过了链接 那我们就先用 redux-thunk 来写一个 asyncTodo 的demo

redux-thunk 分析

import { createStore, applyMiddleware } from 'redux';
const thunk = ({ dispatch, getState }) => next => action => {
  if (typeof action === 'function') {
    return action(dispatch, getState);
  }

  return next(action);
}

const logger = ({ getState }) => next => action => {
  console.log('will dispatch', getState());
  next(action)
  console.log('state after dispatch', getState());
}

const todos = (state = [], action) => {
  switch (action.type) {
    case 'ADD_TODO':
      return [
        ...state,
        action.text
      ];
    default:
      return state
  }
}

const store = createStore(
  todos,
  ['Use Redux'],
  applyMiddleware(logger, thunk),
);

store.dispatch(dispatch => {
  setTimeout(() => {
    dispatch({ type: 'ADD_TODO', text: 'Read the docs' });
  }, 1000);
});
复制代码

原本redux中action只能是 plain object ,redux-thunk使action可以为function。当我们想抛出一个异步的action时,其实我们是把异步的处理放在了actionCreator中。 这样就会导致action形式不统一,并且对于异步的处理将会分散到各个action中,不利于维护。 接下来看看redux-saga是如何实现的

redux-saga

import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { put, take, fork, delay } from './redux-saga/effects'
import { delay as delayUtil } from 'redux-saga/utils';

// 获取redux中间件
const sagaMiddleware = createSagaMiddleware({
  sagaMonitor: {
    // 打印 effect 便于分析redux-saga行为
    effectTriggered(options) {
      console.log(options);
    }
  }
})

function* rootSaga() {
  const action = yield take('ADD_TODO_SAGA');
  // delay(): { type: 'call', payload: { args: [1000], fn }}
  yield delay(1000); // or yield call(delayUtil, 1000)

  // put(): { type: 'PUT', payload: { action: {}, channel: null }}
  yield put({ type: 'ADD_TODO', text: action.text  });
}

const store = createStore(
  todos,
  ['Use Redux'],
  applyMiddleware(logger, sagaMiddleware),
);

// 启动saga
sagaMiddleware.run(rootSaga);

store.dispatch({ type: 'ADD_TODO_SAGA', text: 'Use Redux-saga' });
复制代码

可以看到这里抛出的就是一个纯action, saga在启动之后监听 ADD_TODO_SAGA 事件,若事件发生执行后续代码。

源码

stdChannel

在开始createSagaMiddleware之前,先来了解一下 channel redux-saga 通过 channel 接收与发出action与外部进行数据交换 在redux-saga中有三种 channel,分别是channel、eventChannel、multicastChannel; 在此我们仅仅分析一下用的最多的 multicastChannel

export function multicastChannel() {
  let closed = false
  // 这里taker分为的currentTakers、 nextTakers的原因和redux subscribe类似,防止在遍历taker时,taker发生变化。
  let currentTakers = []
  let nextTakers = currentTakers

  const ensureCanMutateNextTakers = () => {
    if (nextTakers !== currentTakers) {
      return
    }
    nextTakers = currentTakers.slice()
  }

  const close = () => {
    closed = true
    const takers = (currentTakers = nextTakers)

    for (let i = 0; i < takers.length; i++) {
      const taker = takers[i]
      taker(END)
    }

    nextTakers = []
  }

  return {
    [MULTICAST]: true,
    put(input) {

      if (closed) {
        return
      }

      if (isEnd(input)) {
        close()
        return
      }

      const takers = (currentTakers = nextTakers)
      // 遍历takers,找到与input匹配的taker并执行它。
      for (let i = 0; i < takers.length; i++) {
        const taker = takers[i]
        if (taker[MATCH](input)) {
          taker.cancel()
          taker(input)
        }
      }
    },
    // 存下callback,与配置函数
    take(cb, matcher = matchers.wildcard) {
      if (closed) {
        cb(END)
        return
      }
      cb[MATCH] = matcher
      ensureCanMutateNextTakers()
      nextTakers.push(cb)

      cb.cancel = once(() => {
        ensureCanMutateNextTakers()
        remove(nextTakers, cb)
      })
    },
    close,
  }
}

export function stdChannel() {
  const chan = multicastChannel()
  const { put } = chan
  chan.put = input => {
    if (input[SAGA_ACTION]) {
      put(input)
      return
    }
    // 暂时不用管
    asap(() => put(input))
  }
  return chan
}
复制代码

createSagaMiddleware

获取redux-middleware, 同时初始化runsaga函数,为后面启动saga bind 所需的参数

export default function sagaMiddlewareFactory({ context = {}, ...options } = {}) {
  const { sagaMonitor, logger, onError, effectMiddlewares } = options
  let boundRunSaga

  // redux middleware
  function sagaMiddleware({ getState, dispatch }) {
    // 新建一个channel
    const channel = stdChannel()
    channel.put = (options.emitter || identity)(channel.put)

    boundRunSaga = runSaga.bind(null, {
      context,
      channel,
      dispatch,
      getState,
      sagaMonitor,
      logger,
      onError,
      effectMiddlewares,
    })

    return next => action => {
      if (sagaMonitor && sagaMonitor.actionDispatched) {
        sagaMonitor.actionDispatched(action)
      }
      const result = next(action) // hit reducers

      // 将事件传递给saga
      channel.put(action)
      return result
    }
  }

  // 启动saga
  sagaMiddleware.run = (...args) => {
    // ...

    return boundRunSaga(...args)
  }

  //...

  return sagaMiddleware
}
复制代码

runsaga

export function runSaga(options, saga, ...args) {
  
  // generate iterator
  const iterator = saga(...args)

  const {
    channel = stdChannel(),
    dispatch,
    getState,
    context = {},
    sagaMonitor,
    logger,
    effectMiddlewares,
    onError,
  } = options

  const effectId = nextSagaId()

  // 一些错误检查
  // ...

  const log = logger || _log
  const logError = err => {
    log('error', err)
    if (err && err.sagaStack) {
      log('error', err.sagaStack)
    }
  }

  const middleware = effectMiddlewares && compose(...effectMiddlewares)

  // 可以先理解为 finalizeRunEffect = runEffect => runEffect
  const finalizeRunEffect = runEffect => {
    if (is.func(middleware)) {
      return function finalRunEffect(effect, effectId, currCb) {
        const plainRunEffect = eff => runEffect(eff, effectId, currCb)
        return middleware(plainRunEffect)(effect)
      }
    } else {
      return runEffect
    }
  }

  const env = {
    stdChannel: channel,
    dispatch: wrapSagaDispatch(dispatch),
    getState,
    sagaMonitor,
    logError,
    onError,
    finalizeRunEffect,
  }

  // 新建task,作用是控制 Generator 流程,类似与自动流程管理,这个后面会讲到
  const task = proc(env, iterator, context, effectId, getMetaInfo(saga), null)

  return task
}
复制代码

redux-saga的核心就是task, 控制generator函数saga执行流程。是一个复杂的自动流程管理,我们先看一个简单的自动流程管理

// 一个返回promise的delay函数
const delay = (ms) => {
  return new Promise((res) => {
    setTimeout(res, ms);
  });
}

function *main() {
  yield delay(1000);
  console.log('1s later');
  yield delay(2000);
  console.log('done');
}

// 为了达到想要的执行结果,我们必须在promise resolved之后再执行next statement,比如这样
const gen = main();
const r1 = gen.next();
r1.value.then(() => {
  const r2 = gen.next();
  r2.value.then(() => {
    gen.next();
  })
})
复制代码

使用递归实现,自动流程控制

function autoRun(gfunc) {
  const gen = gfunc();

  function next() {
    const res = gen.next();
    if (res.done) return;
    res.value.then(next);
  }

  next();
}
autoRun(main);
复制代码

上面的自动流程控制函数仅仅支持 promise。

proc

export default function proc(env, iterator, parentContext, parentEffectId, meta, cont) {
  // ...

  const task = newTask(parentEffectId, meta, cont)
  const mainTask = { meta, cancel: cancelMain, _isRunning: true, _isCancelled: false }

  // 构建 task tree
  const taskQueue = forkQueue(
    mainTask,
    function onAbort() {
      cancelledDueToErrorTasks.push(...taskQueue.getTaskNames())
    },
    end,
  )

  next()

  // then return the task descriptor to the caller
  return task

  function next(arg, isErr) {
    let result
    if (isErr) {
      result = iterator.throw(arg)
    } else if (shouldCancel(arg)) {
      // ...
    } else if (shouldTerminate(arg)) {
      // ...
    } else {
      result = iterator.next(arg)
    }

    if (!result.done) {
      // 如果没结束, 执行相应 effect
      digestEffect(result.value, parentEffectId, '', next)
    } else {
      /**
        This Generator has ended, terminate the main task and notify the fork queue
      **/
      mainTask._isRunning = false
      mainTask.cont(result.value)
    }
  }

  function digestEffect(effect, parentEffectId, label = '', cb) {
    // 封装了cb函数 增加了事件钩子
    function currCb(res, isErr) {
      if (effectSettled) {
        return
      }

      effectSettled = true
      cb.cancel = noop // defensive measure
      if (env.sagaMonitor) {
        if (isErr) {
          env.sagaMonitor.effectRejected(effectId, res)
        } else {
          env.sagaMonitor.effectResolved(effectId, res)
        }
      }
      if (isErr) {
        crashedEffect = effect
      }
      cb(res, isErr)
    }

    runEffect(effect, effectId, currCb)
  }

  // 每个 effect 的执行函数 这里先看一下常用的几个effect
  function runEffect(effect, effectId, currCb) {
    if (is.promise(effect)) {
      resolvePromise(effect, currCb)
    } else if (is.iterator(effect)) {
      resolveIterator(effect, effectId, meta, currCb)
    } else if (effect && effect[IO]) {
      const { type, payload } = effect
      if (type === effectTypes.TAKE) runTakeEffect(payload, currCb)
      else if (type === effectTypes.PUT) runPutEffect(payload, currCb)
      else if (type === effectTypes.CALL) runCallEffect(payload, effectId, currCb)
      // 其他所有的effect ...
      else currCb(effect)
    } else {
      // anything else returned as is
      currCb(effect)
    }
  }

  // 当返回值是 promise 时,就和之前实现的自动进程控制函数一样嘛
  function resolvePromise(promise, cb) {
    // ...
    promise.then(cb, error => cb(error, true))
  }

  // 当是generator函数时
  function resolveIterator(iterator, effectId, meta, cb) {
    proc(env, iterator, taskContext, effectId, meta, cb)
  }

  // 当是 take 就把callback放在channel里,如果有匹配事件发生,触发 callback
  function runTakeEffect({ channel = env.stdChannel, pattern, maybe }, cb) {
    const takeCb = input => {
      if (input instanceof Error) {
        cb(input, true)
        return
      }
      if (isEnd(input) && !maybe) {
        cb(TERMINATE)
        return
      }
      cb(input)
    }
    try {
      channel.take(takeCb, is.notUndef(pattern) ? matcher(pattern) : null)
    } catch (err) {
      cb(err, true)
      return
    }
    cb.cancel = takeCb.cancel
  }

  function runPutEffect({ channel, action, resolve }, cb) {
    asap(() => {
      let result
      try {
        // 发送 action
        result = (channel ? channel.put : env.dispatch)(action)
      } catch (error) {
        cb(error, true)
        return
      }

      if (resolve && is.promise(result)) {
        resolvePromise(result, cb)
      } else {
        cb(result)
      }
    })
    // put 是不能取消的
  }
  
  function runCallEffect({ context, fn, args }, effectId, cb) {
    let result

    try {
      result = fn.apply(context, args)
    } catch (error) {
      cb(error, true)
      return
    }
    return is.promise(result)
      ? resolvePromise(result, cb)
      : is.iterator(result)
        ? resolveIterator(result, effectId, getMetaInfo(fn), cb)
        : cb(result)
  }
}
复制代码

总结

redux-saga 将异步操作抽象为 effect,利用 generator 函数,控制saga流程。 到目前为止,只是涉及了一些基本流程,下一篇会对本篇进行补充。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

A Philosophy of Software Design

A Philosophy of Software Design

John Ousterhout / Yaknyam Press / 2018-4-6 / GBP 14.21

This book addresses the topic of software design: how to decompose complex software systems into modules (such as classes and methods) that can be implemented relatively independently. The book first ......一起来看看 《A Philosophy of Software Design》 这本书的介绍吧!

图片转BASE64编码
图片转BASE64编码

在线图片转Base64编码工具

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

Markdown 在线编辑器
Markdown 在线编辑器

Markdown 在线编辑器