开始使用
redux-saga项目中example文件夹下counter示例。
为调试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
执行时传入的getState
和 dispatch
并返回以下格式的方法
return next => action => {
if (sagaMonitor && sagaMonitor.actionDispatched) {
sagaMonitor.actionDispatched(action)
}
const result = next(action) // hit reducers
channel.put(action)
return result
}
此方法会被 rudex applyMiddleware
中compose
串联到一起,详情可在网上搜索。
如果存在其他中间件 next
则是被中间件处理并返回此 (action=>{return action})
格式的方法
熟悉redux
会知道这是dispatch
方法的格式,
如果不存在其他中间件 则next
是原始redux
的dispatch
redux 中applyMiddleware方法内部compose功能解析
redux compose
用Array.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
中间件b
中 b
里头的next
既是被c
中间件包装后返回的方法,可推测a
中next
是b
中间件包装后返回的方法
开始使用saga
回到saga 代码中
function sagaMiddleware({ getState, dispatch }) {
boundRunSaga = runSaga.bind(null, {
...options,
context,
channel,
dispatch,
getState,
sagaMonitor,
})
......}
将boundRunSaga
赋值为runsaga
绑定一个对象参数的方法,其中channel
和dispatch
是重要参数。
当我们使用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
})
immediately
为saga
中scheduler.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
值。
四种情况:
- yield promise,effect是一个promise
- yield *generator() effect是一个迭代器
- yield effect(func) 返回的是被saga effect标记的value 其中会有IO=@@redux-saga/IO属性 值为true
- 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
处理后的对象如下图所示
重要的属性:
@@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
处理并返回此图中的对象。
回到第三种情况的代码段:
此时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
方法,上述模拟的迭代器有其自定义的next
和throw
所以通过,直接返回。
回到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
,其值如下图所示:
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)
我们得知matcher
为matcher(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简要执行流程:
开始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
只是将multicastChannel
的put
方法进行了包装,我们只要关注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的内容)
q1
中yTake
最终调用了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
,然后在proc
内next
执行时到达上述说的四种情况的第三种,消化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的作用。