redux-saga学习笔记

606 阅读16分钟

开始使用

redux-saga项目中example文件夹下counter示例。

image.png 为调试saga 我引入了项目中的saga源码

从创建saga中间件开始

上图中调用createSagaMw实际上调用sagaMiddlewareFactory,一般使用时无参数,内部有默认参数

export default function sagaMiddlewareFactory({ context = {}, channel = stdChannel(), sagaMonitor, ...options } = {}) {
  let boundRunSaga

  if (process.env.NODE_ENV !== 'production') {
    check(channel, is.channel, 'options.channel passed to the Saga middleware is not a channel')
  }

  function sagaMiddleware({ getState, dispatch }) {
    boundRunSaga = runSaga.bind(null, {
      ...options,
      context,
      channel,
      dispatch,
      getState,
      sagaMonitor,
    })

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

  sagaMiddleware.run = (...args) => {
    if (process.env.NODE_ENV !== 'production' && !boundRunSaga) {
      throw new Error('Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware')
    }
    
    return boundRunSaga(...args)
  }

  sagaMiddleware.setContext = props => {
    if (process.env.NODE_ENV !== 'production') {
      check(props, is.object, createSetContextWarning('sagaMiddleware', props))
    }

    assignWithSymbols(context, props)
  }

  return sagaMiddleware
}

调用sagaMiddlewareFactory返回一个符合redux标准的中间件,当中间件融入redux系统中时会接受redux applyMiddleware执行时传入的getStatedispatch 并返回以下格式的方法

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

此方法会被 rudex applyMiddlewarecompose串联到一起,详情可在网上搜索。

如果存在其他中间件 next则是被中间件处理并返回此 (action=>{return action})格式的方法 熟悉redux 会知道这是dispatch方法的格式, 如果不存在其他中间件 则next是原始reduxdispatch

redux 中applyMiddleware方法内部compose功能解析

redux composeArray.reduce((a,b)=>(...args)=>a(b(...args)))巧妙的将各个中间件连接到一起。 执行源码:

//redux compose 内部代码片段
  function compose(...funcs: Function[]) {
  if (funcs.length === 0) {
    // infer the argument type so it is usable in inference down the line
    return <T>(arg: T) => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce(
    (a, b) =>
      (...args: any) =>
        a(b(...args))
  )
}
  compose(...chain/*中间件被保存的数组*/)(store.dispatch/*redux原本的dispatch方法*/)

此执行逻辑是: 有a(next=>action=>{})b(next=>action=>{})c(next=>action=>{})三个中间件 被reduce处理后返回(...args)=>a(b(...args) 方法。,然后此结果再与c方法结合则是(...arg)=>((...arg)=>a(b(...args))(c(...args))

然后传入store.dispatch 方法调用 (store.dispatch)-->((...arg)=>a(b(...args))(c(store.dispatch))-->(c/*action={}*/)=>a(b(c/*action=>{}c中间件返回的方法*/)-->a(b/*action=>{}b中间件返回的方法*/)-->action=>{}/*a中间件返回的方法*/返回dispatch格式的方法。

由此可见c中间件的next中传入的是redux原始的dispatch,返回一个dispatch格式的方法,传入到b中间件bb里头的next既是被c中间件包装后返回的方法,可推测anextb中间件包装后返回的方法

开始使用saga

回到saga 代码中

function sagaMiddleware({ getState, dispatch }) { 
  boundRunSaga = runSaga.bind(null, {
      ...options,
      context,
      channel,
      dispatch, 
      getState,
     sagaMonitor, 
})
......}

boundRunSaga 赋值为runsaga 绑定一个对象参数的方法,其中channeldispatch是重要参数。

当我们使用saga 执行sagaMiddleware.run(rootSaga)

  sagaMiddleware.run = (...args) => {
    if (process.env.NODE_ENV !== 'production' && !boundRunSaga) {
      throw new Error('Before running a Saga, you must mount the Saga middleware on the Store using applyMiddleware')
    }
    
    return boundRunSaga(...args)
  }

实际执行的就是:

runSaga.bind(null, {
      ...options,
      context,
      channel,
      dispatch, 
      getState,
     sagaMonitor, 
})(...args)

runSaga 代码片段及分析

export function runSaga(
  { channel = stdChannel(), dispatch, getState, context = {}, sagaMonitor, effectMiddlewares, onError = logError },
  saga,//此saga为用户自定义saga,生成器函数
  ...args//为给saga内的导出的生成器函数传值
) {
  const iterator = saga(...args)//执行用户自定义的生成器函数返回一个迭代器
  
   let finalizeRunEffect=identity //identity = v => v 传啥返回啥。。。
   
   const env = {
    channel,
    dispatch: wrapSagaDispatch(dispatch),//包装一下
    getState,
    sagaMonitor,
    onError,
    finalizeRunEffect,//saga关键功能方法proc会用到
  }
  
  return immediately(() => {
    const task = proc(env, iterator, context, effectId, getMetaInfo(saga), /* isRoot */ true, undefined)
    return task
  })
  

immediatelysagascheduler.js中方法之一,没什么可说的。 其中主要是proc这个方法它是saga主要功能的代码 proc方法则是对生成器函数返回的迭代器进行处理,内部next方法执行迭代器的next,控制生成器函数的执行,可以被暂时停止。

proc方法功能

proc代码片段:

function proc(env, iterator, parentContext, parentEffectId, meta, isRoot, cont) {
       //以上参数成为闭包,proc中主要执行是其内部的next和runEffect,这两个方法会使用以上参数 其中env,和iterator最为重要
       const finalRunEffect = env.finalizeRunEffect(runEffect)//identity = v => v执行后finalRunEffect=runEffect
       
       next()//迭代器执行
       
       function next(arg, isErr) {
          let result
          
          result = iterator.next(arg) //执行迭代
          if (!result.done) {
              //如果迭代器未执行完则消化effect,effect为saga中effect内的方法
              //此counter示例代码使用的是takeEvery
              digestEffect(result.value, parentEffectId, next)
        } else {
            /*做一些收尾工作*/
       }
      }
      
      function digestEffect(effect, parentEffectId, cb, label = '') {
             function currCb(res, isErr) {
                  /*做了一些简单判断*/
                  cb(res,isErr)//cb 是next方法
             }
           finalRunEffect(effect, effectId, currCb)//实际上是执行runEffect
      }
      
      function runEffect(effect, effectId, currCb /* 被包装后的next执行下次迭代 */) {
            if (is.promise(effect)) {//effect是一个promise
                   resolvePromise(effect, currCb)
           } else if (is.iterator(effect)) {//effect是一个迭代器
              // resolve iterator
              proc(env, effect, task.context, effectId, meta, /* isRoot */ false, currCb)
           } else if (effect && effect[IO]) {
               const effectRunner = effectRunnerMap[effect.type]
               effectRunner(env, effect.payload, currCb, executingContext)//如果take类型则执行暂时结束等到dispatch发生则继续执行
          } else {
            // anything else returned as is
           currCb(effect)
          }
      }
}

runEffect功能解析

注意看代码中runEffect方法 runEffect 中有三个主要执行的地方,看代码片段runEffect 方法第一个参数实际上是迭代器返回的{value,done}格式对象中的value值。

四种情况:

  1. yield promise,effect是一个promise
  2. yield *generator() effect是一个迭代器
  3. yield effect(func) 返回的是被saga effect标记的value 其中会有IO=@@redux-saga/IO属性 值为true
  4. yield 是一个普通函数或者基本值

这里比较有意思的是对promise处理,在使用saga时经常会见到这样的代码: const data=yield asyncFetch() 细心会觉得很有意思为什么data可以获取到异步返回的res数据,可以看到这段代码和async/await语法格式几乎一模一样。其实现原理重点在resolvePromise

resolvePromise是实现async/await语法的关键

resolvePromise代码片段:

function resolvePromise(promise, cb) {
 //cb既是next 可以知道此处执行next(res)
  promise.then(cb, error => {
    cb(error, true)
  })
}

由此不难看出将迭代器下一次执行的代码放到了promise then里,而next方法内部执行iterator.next(res)res传给了data,完成了data赋值。

回到counter示例使用saga的代码:

import { put, takeEvery, delay } from '../../../../packages/core/src/effects'

export function* incrementAsync() {
  
  yield delay(1000)
 
  yield put({ type: 'INCREMENT' })
}

export default function* rootSaga() {
  
  yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}

可以得知当执行sagaMiddleware.run(rootSaga)时执行的是上面四种情况的第三种情况。
一个被takeEvery处理的迭代器返回值,也就是当迭代器执行上面 yield takeEvery('INCREMENT_ASYNC', incrementAsync)到这一步返回{value:takeEvery('INCREMENT_ASYNC', incrementAsync),done:false}

redux-saga/effect takeEvery方法功能解析

takeEvery处理后的对象如下图所示

image.png

重要的属性: @@redux-saga/IO,为true时则继续执行根据对象内的type,来执行相对应的处理方法这里是"FORK"。 fn,将要执行的方法注意:fn里头的takeEvery不是rootSaga使用的那个takeEvery,此方法在saga中很关键稍后会说明

rootSaga 中的takeEvery内部代码片段(在io-helpers.js)

function takeEvery(patternOrChannel, worker, ...args) {
  //patternOrChannel 是action.type, worker是用户自定义的生成器函数
  return fork(takeEveryHelper/*即为上图中payload属性下fn属性的那个takeEvery, */patternOrChannel, worker, ...args)
}

fork源代码及功能描述

可以看到执行了fork方法 fork定义在io.js中 以下为方法代码片段:

function fork(fnDescriptor, ...args) {
  return makeEffect(effectTypes.FORK, getFnCallDescriptor(fnDescriptor, args)/* { context, fn=takeEvery.js, args } */)
}

先看下getFnCallDescriptor:

  function getFnCallDescriptor(fnDescriptor/*takeEveryHelper*/,
    args/*patternOrChannel 既是rootSaga中INCREMENT_ASYNC,worker 既是用户自定义的saga方法 这里是incrementAsync,如果args长度超过2,则第三个参数则是自定义传给incrementAsync的参数*/
  
  ) {
  let context = null
  let fn

  if (is.func(fnDescriptor)) {
    fn = fnDescriptor
  } else {
    if (is.array(fnDescriptor)) {
      ;[context, fn] = fnDescriptor
    } else {
      ;({ context, fn } = fnDescriptor)
    }

    if (context && is.string(fn) && is.func(context[fn])) {
      fn = context[fn]
    }
  }

  return { context/*null*/, fn/*takeEveryHelper既是takeEvery.js 定义的takeEvery方法*/,args/*['INCREMENT_ASYNC',incrementAsync] }
}

makeEffect 所做的事

再来看看makeEffect:

const makeEffect = (type, payload /*是上面getFnCallDescriptor返回的对象*/) => ({
  [IO]: true,
  combinator: false,
  type,
  payload,
})

由此我们得知不同类型的effect 最终会经过makeEffect处理并返回此图中的对象。 image.png

回到第三种情况的代码段:
此时effect为上图中返回的数据

else if (effect && effect[IO]) {
               const effectRunner = effectRunnerMap[effect.type]
               effectRunner(env, effect.payload, currCb, executingContext)//如果take类型则执行暂时结束等到dispatch发生则继续执行
          } 

看看effectRunnerMap的代码内容(在effectRunnerMap.js中):

  const effectRunnerMap = {
  [effectTypes.TAKE]: runTakeEffect,
  [effectTypes.PUT]: runPutEffect,
  [effectTypes.ALL]: runAllEffect,
  [effectTypes.RACE]: runRaceEffect,
  [effectTypes.CALL]: runCallEffect,
  [effectTypes.CPS]: runCPSEffect,
  [effectTypes.FORK]: runForkEffect,
  [effectTypes.JOIN]: runJoinEffect,
  [effectTypes.CANCEL]: runCancelEffect,
  [effectTypes.SELECT]: runSelectEffect,
  [effectTypes.ACTION_CHANNEL]: runChannelEffect,
  [effectTypes.CANCELLED]: runCancelledEffect,
  [effectTypes.FLUSH]: runFlushEffect,
  [effectTypes.GET_CONTEXT]: runGetContextEffect,
  [effectTypes.SET_CONTEXT]: runSetContextEffect,
}

我们知道此时effect.type="FORK",所以effectRunner=runForkEffect

runForkEffect源码及功能解析

看看runForkEffect内部代码片段:


//在effectRunnerMap.js中
function runForkEffect(env, { context, fn /* 如果用了takeEvery则是takeEvery.js创造的迭代器 */, args, detached }, cb, { task: parent }) {
  const taskIterator = createTaskIterator({ context, fn, args })//args 是action.type和自定义生成器

  immediately(() => {
    const child = proc(env, taskIterator, parent.context, currentEffectId, meta, detached, undefined)

  })
  // Fork effects are non cancellables
}

createTaskIterator方法功能解析

createTaskIterator方法创建一个迭代器以用来传给下面的proc方法,上述已说明proc方法让传给它的迭代器执行迭代。
下面是createTaskIterator方法片段:

//在effectRunnerMap.js中
function createTaskIterator({ context, fn/*takeEvery*/, args/* ['INCREMENT_ASYNC',incrementAsync]*/}) {
    const result = fn.apply(context, args)
    //如果是迭代器则直接返回
    if (is.iterator(result)) {
      return result
    }

    let resolved = false
   //模拟es6迭代器的next方法 可以看到其返回符合es6next方法执行后返回的数据格式{value,done}
    const next = arg => {
      if (!resolved) {
        resolved = true
        // 只有从fork中返回的promise会被处理,可以看到如果返回的是promise的值则认为迭代器没有迭代结束
        return { value: result, done: !is.promise(result) }
      } else {
        return { value: arg, done: true }
      }
    }

    return makeIterator(next)
  } 

saga-Helper中takeEvery源码及功能解析,saga重点!

我们先来看看takeEvery的代码:

function takeEvery(patternOrChannel /* action.type */, worker /* 自定义生成器 */, ...args) {
  const yTake = { done: false, value: take(patternOrChannel) /* {@@redux-saga/IO: true, combinator: false, type: 'TAKE', payload:{ pattern: patternOrChannel } } */ }
  const yFork = ac => ({ done: false, value: fork(worker, ...args, ac) })

  let action,
    setAction = ac => (action = ac)
 
  return fsmIterator(
    {
      q1() {
        return { nextState: 'q2', effect: yTake, stateUpdater: setAction }
      },
      q2() {
        return { nextState: 'q1', effect: yFork(action) }
      },
    },
    'q1',
    `takeEvery(${safeName(patternOrChannel)}, ${worker.name})`,
  )
}

注意yTake的值{@@redux-saga/IO: true, combinator: false, type: 'TAKE', payload:{ pattern: patternOrChannel } }
其过程和上述执行effect处理基本一样仍是将传入的值进行包装,然后effectRunner进行针对性处理
看一下take做了什么

    function take(patternOrChannel = '*', multicastPattern) {
  //和上面用户用的takeEvery effect过程一样,调用makeEffect进行包装,而take的功能是将匹配字段也就是redux 里头action.type保存起来,下面将说明此过程
    return makeEffect(effectTypes.TAKE, { channel: patternOrChannel })
  }

fsmIterator方法功能解析

fsmIterator方法代码片段:

//定义在fsmIterator.js
function fsmIterator(fsm, startState/*q1*/, name) {
  let stateUpdater,
    errorState,
    effect,
    nextState = startState

  function next(arg, error) {
    if (nextState === qEnd) {
      return done(arg)
    }
    if (error && !errorState) {
      nextState = qEnd
      throw error
    } else {
      stateUpdater && stateUpdater(arg)
      const currentState = error ? fsm[errorState](error) : fsm[nextState]()
      ;({ nextState, effect, stateUpdater, errorState } = currentState)
      return nextState === qEnd ? done(arg) : effect
    }
  }

  return makeIterator(next, error => next(null, error), name)
  }

makeIterator代码:

function makeIterator(next, thro = kThrow, name = 'iterator') {
  const iterator = { meta: { name }, next, throw: thro, return: kReturn, isSagaIterator: true }
  return iterator
}

由此可见返回的是一个模拟迭代器的对象其内部有next方法,我们可以像使用迭代器一样使用它
回到createTaskIterator方法中

 if (is.iterator(result)) {
      return result
    }

is.iterator判读是否是迭代器的逻辑是是否存在next方法和throw方法,上述模拟的迭代器有其自定义的nextthrow所以通过,直接返回。
回到runForkEffect

function runForkEffect(env, { context, fn /* 如果用了takeEvery则是takeEvery.js创造的迭代器 */, args, detached }, cb, { task: parent }) {
  const taskIterator = createTaskIterator({ context, fn, args })//args 是action.type和自定义生成器

  immediately(() => {
    const child = proc(env, taskIterator, parent.context, currentEffectId, meta, detached, undefined)

  })
  // Fork effects are non cancellables
}

saga-helper 中takeEvery模拟迭代器,使用fsmIterator制作,关键点在于其内部next方法

此时taskIterator是saga源码中模拟的迭代器,被传入proc方法中就会执行其自定义的next方法 next方法代码片段:

//在方法fsmIterator
 /*fsm为{
      q1() {
        return { nextState: 'q2', effect: yTake, stateUpdater: setAction }
      },
      q2() {
        return { nextState: 'q1', effect: yFork(action) }
      }*/
 /*nextState 被传进来的startState赋值此时为'q1'*/
     function next(arg, error) {
    if (nextState === qEnd) {
      return done(arg)
    }
    if (error && !errorState) {
      nextState = qEnd
      throw error
    } else {
      stateUpdater && stateUpdater(arg)
      const currentState = error ? fsm[errorState](error) : fsm[nextState]()
      ;({ nextState, effect, stateUpdater, errorState } = currentState)
      return nextState === qEnd ? done(arg) : effect
    }
  }

重点看这行代码const currentState = error ? fsm[errorState](error) : fsm[nextState]() 没有错误执行fsm['q1']() ,由此返回{ nextState: 'q2', effect: yTake, stateUpdater: setAction }
解构currentState 获取effect,effect即为yTake,其值如下图所示: image.png proc方法中主要做两件事情一个是迭代器执行迭代,另一个是对迭代器返回的值进行处理,内部代码成为digestEffect。
此时又回到上述四种情况的流程了,并执行四种情况的第三种

//在proc方法内部runEffect代码中
else if (effect && effect[IO]) {
               const effectRunner = effectRunnerMap[effect.type]
               effectRunner(env, effect.payload, currCb, executingContext)//如果take类型则执行暂时结束等到dispatch发生则继续执行
          } 

其中effect.type类型为'take',执行runTakeEffect,runTakeEffect代码

runTakeEffect方法代码和其功能解析

function runTakeEffect(env, { channel = env.channel, pattern, maybe }, cb) {
//cb为proc内部effect方法包装其内部的next方法
//这里又对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) /* 自定义saga的pattern会与接下来dispatch的action.type匹配 */ : null)
  } catch (err) {
    cb(err, true)
    return
  }
  cb.cancel = takeCb.cancel
}

saga另一重要模块channel功能解析

重点看channel.take(takeCb, is.notUndef(pattern) ? matcher(pattern) : null)这行代码
channel是一开始在使用saga时 默认的参数,此参数一直向下传递

function sagaMiddlewareFactory({ context = {}, channel = stdChannel(), sagaMonitor, ...options } = {}) {...}

channel是执行stdChannel()返回的结果,来看stdChannel代码

//在channel.js中
function stdChannel() {
  const chan = multicastChannel()
  const { put } = chan
  chan.put = input => {
    if (input[SAGA_ACTION]) {
      put(input)
      return
    }
  
    asap(() => {
      put(input)
    })
  }
  return chan
}

我们不难看到channel主要是multicastChannel()返回的结果,看multicastChannel()代码片段,这里重点看take方法中的cb[MATCH] = matcher

   function multicastChannel() {
  let closed = false
  let currentTakers = []
  let nextTakers = currentTakers

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

      const takers = (currentTakers = nextTakers)

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

        if (taker[MATCH](input)) {
          taker.cancel()
          taker(input)
        }
      }
    },
    take(cb, matcher = matchers.wildcard) {
    //cb 为包装后proc内部next方法
      cb[MATCH] = matcher
      ensureCanMutateNextTakers()
      nextTakers.push(cb)//将回调存到任务栈中

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

回到这断代码channel.take(takeCb, is.notUndef(pattern) ? matcher(pattern) : null)我们得知matchermatcher(pattern)其中pattern现在为'INCREMENT_ASYNC'

redux-saga/effect 中takeEvery第一入参与action.type进行比对的关键代码

matcher方法代码片段:

   const string = pattern => input => input.type === String(pattern)

因为我们的pattern是string类型所有执行的是上面的代码,返回的方法保存了pattern值成为闭包,等待传入input,与input.type进行比对,返回比对结果。
所以cb[MATCH] = matcher在cb方法中定义MATCH属性赋值这个比对方法。
接下来nextTakers.push(cb)将此cb存入channel内部的数组中,此时take功能执行完毕。 于此同时我们的方法调用栈是proc方法中runEffect内effectRunner(runTakeEffect)-->channel.take当take执行完时,回到了proc代码中通过源码我们看到当effectRunner执行结束后,proc代码也执行完毕。

由此sagaMiddleware.run(rootSaga)执行结束。

saga简要执行流程:

saga流程图.png

开始dispatch

由于saga中间件已被融入到redux中所以调用dispatch时实际执行的是:

   //代码在middleware.js中
   //dispatch方法格式为action=>action,凡是满足此格式的都可以作为dispatch
   action => {
      if (sagaMonitor && sagaMonitor.actionDispatched) {
        sagaMonitor.actionDispatched(action)
      }
      console.log(next,action,dispatch,'dispatch')
      const result = next(action) // hit reducers
      
      channel.put(action)
      return result
    }

const result = next(action) 执行了dispatch,如果你的reducers存在以下代码则在调用takeEvery时会执行

   function* rootSaga() {
      yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
//takeEvery方法的第一参数与reduce响应的action.type相同
    function reducer(state = 0, action) {
       switch (action.type) {
          case 'INCREMENT_ASYNC':
           console.log('haha捕获到')
           return state
       default:
           return state
  }
}

channel功能描述补充,channel.put功能解析

接下来到了关键点 channel.put,我们知道channel是通过stdChannel生成的

function stdChannel() {
  const chan = multicastChannel()
  const { put } = chan
  chan.put = input => {
    if (input[SAGA_ACTION]) {
      put(input)
      return
    }
  
    asap(() => {
      put(input)
    })
  }
  return chan
}

由此得知stdChannel只是将multicastChannelput方法进行了包装,我们只要关注multicastChannel内部的put方法就可以了

// multicastChannel里完整代码
function multicastChannel() {
  let closed = false
  let currentTakers = []
  let nextTakers = currentTakers

  function checkForbiddenStates() {
    if (closed && nextTakers.length) {
      throw internalErr(CLOSED_CHANNEL_WITH_TAKERS)
    }
  }

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

  const close = () => {
    if (process.env.NODE_ENV !== 'production') {
      checkForbiddenStates()
    }

    closed = true
    const takers = (currentTakers = nextTakers)
    nextTakers = []
    takers.forEach(taker => {
      taker(END)
    })
  }

  return {
    [MULTICAST]: true,
    put(input) {
      if (process.env.NODE_ENV !== 'production') {
        checkForbiddenStates()
        check(input, is.notUndef, UNDEFINED_INPUT_ERROR)
      }

      if (closed) {
        return
      }

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

      const takers = (currentTakers = nextTakers)//cb 为next包装存到下一个任务中

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

        if (taker[MATCH](input)) {
          taker.cancel()
          taker(input)
        }
      }
    },
    take(cb, matcher = matchers.wildcard) {
      if (process.env.NODE_ENV !== 'production') {
        checkForbiddenStates()
      }
      if (closed) {
        cb(END)
        return
      }
      cb[MATCH] = matcher
      ensureCanMutateNextTakers()
      nextTakers.push(cb)

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

channel.put主要功能的关键代码解析

回到用户saga代码:action('INCREMENT_ASYNC')实际返回的是{type:'INCREMENT_ASYNC'}上文已说dispatch实际上执行了channel.put(action) action的值既是{type:'INCREMENT_ASYNC'},因此传给multicastChannel.put的input就是这个action。我们主要看下面代码

 const takers = (currentTakers = nextTakers)//cb 为next包装存到下一个任务中

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

        if (taker[MATCH](input)) {
          taker.cancel()
          taker(input)
        }
      }

nextTaker这个数组是由执行take方法时将proc内部的next方法存入到这个数组中,而proc又保存了next所需的iterator,所以执行put方法时,就是将在take方法中暂停的迭代继续执行。
现在可以向上翻看看taker[MATCH]是什么,怎么求出来的。 简要说下,此时taker[MATCH]存了由用户rootSaga代码中yield takeEvery('INCREMENT_ASYNC', incrementAsync)takeEvery方法中的第一参数。当用户发起一个action,则用action.type和其比对如果成功则执行taker(input),也就是继续迭代。

此时taker是哪个迭代器在执行?还记得q1,q2么?就是那个在saga源码中模拟的迭代器,功能在takeEvery.js中由fsmIterator制作。(详情请看标题含有saga-helper的内容) q1yTake最终调用了channel.take存储匹配,并暂停迭代器执行。当用户发起action时继续执行执行迭代器,q1的下个迭代状态是q2
我们来看看q2返回的值:const yFork = ac => ({ done: false, value: fork(worker, ...args, ac) })

重点看看fork这个方法,其入参为worker(用户自定义的生成器函数,这里是incrementAsync) args为需要传入这个生成器函数的参数,ac为用户派发的action。 这里的fork的方法为io方法在说明sagaMiddleware.run(rootSaga)过程时讲述过 (详情请看fork),这里简要总结一下,io中的fork包装一下,返回effect,然后在procnext执行时到达上述说的四种情况的第三种,消化effect,实际上提取出存在effect内存储的worker(此时是用户自定义的incrementAsync生成器函数)执行createTaskIterator方法返回一个迭代器(生成器函数执行就返回迭代器,此方法主要考虑了异步promise的情况,如果是返回的是promise则认为其没有迭代完,我们知道promise接下来的结果还会在then执行)。
此时将这个迭代器传入proc执行迭代,又重复上述过程,当用户的自定义的生成器函数incrementAsync迭代结束,相关方法开始出调用栈。又回到了q1,q2,因为q2的结果返回过了,这个模拟迭代器的下一个状态就是q1,返回q1结果则又执行了channel.take将匹配字段方法又存到了cb[MATCHED]中。(对此描述有疑惑,请看上面 redux-saga/effect 中takeEvery第一入参与action.type进行比对的关键代码

比较巧妙的是takeEvery中的q1,q2(其他sagaHelper中也有类似的代码,不同的是像takeLatest存在q3),saga依靠它运转。它就像是弓箭一样,当用户sagaMiddleware.run(rootSaga)时拉弦,当用户发起action时射出,然后再度拉弦,等待发射。

put方法源码,功能解析

在使用示例代码中

  function* incrementAsync() {
  
  yield delay(1000)
  yield put({ type: 'INCREMENT' })
}

这里的put实际调用了saga作为redux中间件时传入的dispatch

function put(channel /* 一般为action */, action) {
  return makeEffect(effectTypes.PUT, { channel, action })
}

仍是返回了effect,所以直接看effectRunnerMap.js中的代码

  function runPutEffect(env, { channel, action, resolve }, cb) {
  /**
   Schedule the put in case another saga is holding a lock.
   The put will be executed atomically. ie nested puts will execute after
   this put has terminated.
   **/
 
  asap(() => {
    let result
    try {
     
      //dispatch 会返回action
      result = (channel ? channel.put : env.dispatch)(action)
      //channel有可能是派发的action但被赋值给action这个参数了,这时channal被赋值为undefined
    } catch (error) {
      cb(error, true)
      return
    }

    if (resolve && is.promise(result)) {
      resolvePromise(result, cb)
    } else {
   
      cb(result)
    }
  })

注意这行代码 result = (channel ? channel.put : env.dispatch)(action)而env.dispatch是由

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

这时绑定传入的,也就是用户sagaMiddleware.run(rootSaga)的时候。
由此saga源码基本流程的解析结束。下一篇将讲述saga的一些细节以及其他effect的作用。