开始使用
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的作用。