Begin to use
Example counter in the Example folder of the Redux-Saga project.
To debug saga, I imported the saga source code from the project
Start by creating the Saga middleware
The createSagaMw call in the figure above actually calls sagaMiddlewareFactory, which is normally used with no arguments and has default arguments inside
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')}returnboundRunSaga(... args) } sagaMiddleware.setContext =props= > {
if(process.env.NODE_ENV ! = ='production') {
check(props, is.object, createSetContextWarning('sagaMiddleware', props))
}
assignWithSymbols(context, props)
}
return sagaMiddleware
}
Copy the code
The call to sagaMiddlewareFactory returns a redux-compliant middleware that, when integrated into a Redux system, accepts the getState and Dispatch passed in when Redux applyMiddleware executes and returns methods in the following format
return next= > action= > {
if (sagaMonitor && sagaMonitor.actionDispatched) {
sagaMonitor.actionDispatched(action)
}
const result = next(action) // hit reducers
channel.put(action)
return result
}
Copy the code
This method is cascaded together by Rudex applyMiddleware compose, which can be found online.
If other middleware exists, next is processed by the middleware and returns this (Action =>{return Action}) format. Familiar with Redux, this is the format of the dispatch method. If no other middleware exists, next is the dispatch of the original Redux
Redux: Compose inside the applyMiddleware method
Redux compose with Array. Reduce ((a, b) = > (… args)=>a(b(… Args))) cleverly link the middleware together. Execute source code:
// Redux compose internal snippet
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/* The array the middleware is saving */)(store.dispatch/* Redux's original dispatch method */)
Copy the code
(next=> Action =>{}) (next=> Action =>{}) (next=> Action =>{}) args)=>a(b(… The args) method.
We then pass in the store.dispatch method call ((… args)=>a(b(… Args)) (store. Dispatch) — > a (b (store. Dispatch) — > a (action = > {} / / middleware return method b) – > action = > {} / * a middleware return method * / return dispatch method of format.
It can be seen that the original Dispatch of Redux is passed in next of B middleware, and a method of dispatch format is returned. The next passed in A of A in A of A middleware is the method returned after being packaged by B middleware.
Start using Saga
Back to the Saga code
function sagaMiddleware({ getState, dispatch }) {
boundRunSaga = runSaga.bind(null, {... options, context, channel, dispatch, getState, sagaMonitor, }) ...... }Copy the code
Method of binding boundRunSaga to runSaga with an object parameter, where channel and Dispatch are important parameters.
When we execute sagamiddleware. run(rootSaga) with saga
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')}returnboundRunSaga(... args) }Copy the code
What is actually implemented is:
runSaga.bind(null, {... options, context, channel, dispatch, getState, sagaMonitor, })(... args)Copy the code
RunSaga code snippets and analysis
export function runSaga(
{ channel = stdChannel(), dispatch, getState, context = {}, sagaMonitor, effectMiddlewares, onError = logError },
saga,// This saga is a user - defined saga generator function. args// pass values to exported generator functions in saga
) {
constiterator = saga(... args)// Executing a user-defined generator function returns an iterator
let finalizeRunEffect=identity //identity = v => v returns what?
const env = {
channel,
dispatch: wrapSagaDispatch(dispatch),// Wrap it up
getState,
sagaMonitor,
onError,
finalizeRunEffect,The //saga key function method is used in proc
}
return immediately(() = > {
const task = proc(env, iterator, context, effectId, getMetaInfo(saga), /* isRoot */ true.undefined)
return task
})
Copy the code
Immediately is one of the scheduler.js methods in Saga, nothing to say. The proc method processes the iterator returned by the generator function, and the internal next method performs the next of the iterator, controlling the execution of the generator function, and can be temporarily stopped.
Proc method functionality
Proc code snippet:
function proc(env, iterator, parentContext, parentEffectId, meta, isRoot, cont) {
// these arguments become closures. The main implementations in proc are next and runEffect, which use these arguments, env and iterator being the most important
const finalRunEffect = env.finalizeRunEffect(runEffect)//identity = v => v after finalRunEffect=runEffect
next()// The iterator executes
function next(arg, isErr) {
let result
result = iterator.next(arg) // Perform iteration
if(! result.done) {// If the iterator is not finished, digest effect. Effect is the effect method in saga
// This counter sample code uses takeEvery
digestEffect(result.value, parentEffectId, next)
} else {
/* Tie up some loose ends */}}function digestEffect(effect, parentEffectId, cb, label = ' ') {
function currCb(res, isErr) {
/* Made some simple judgments */
cb(res,isErr)//cb is the next method
}
finalRunEffect(effect, effectId, currCb)// Actually execute runEffect
}
function runEffect(effect, effectId, currCb /* The wrapped next performs the next iteration */) {
if (is.promise(effect)) {//effect is a promise
resolvePromise(effect, currCb)
} else if (is.iterator(effect)) {//effect is an iterator
// 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)// If the take type is used, the execution is temporarily terminated until the dispatch occurs
} else {
// anything else returned as is
currCb(effect)
}
}
}
Copy the code
RunEffect function analysis
Notice that the runEffect method in this code has three main execution places. The first argument to the runEffect method is actually a value in the {value,done} format object returned by the iterator.
Four cases:
- Yield promise, effect is a promise
- Yield *generator() effect is an iterator
- Yield Effect (func) returns the value marked by saga effect where IO=@@redux-saga/IO property is true
- Yield is a normal function or base value
What’s interesting here is the handling of promises. You’ll often see code like this when using saga: Const data=yield asyncFetch() Const data=yield asyncFetch() Const data=yield asyncFetch() Const data=yield asyncFetch() Const data=yield asyncFetch() Its realization principle focuses on resolvePromise
ResolvePromise is to achieveasync/await
The key to Grammar
ResolvePromise code snippet:
function resolvePromise(promise, cb) {
// next(res)
promise.then(cb, error= > {
cb(error, true)})}Copy the code
The next method internally executes iterator.next(res) passes res to data, completing the data assignment.
Back to the counter example using the saga code:
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)
}
Copy the code
You can see that when sagamiddleware.run (rootSaga) is executed, the third of the above four cases is executed. An iterator returned by takeEvery, Yield takeEvery(‘INCREMENT_ASYNC’, incrementAsync) returns {value:takeEvery(‘INCREMENT_ASYNC’, IncrementAsync), done: false}.
Redux-saga /effect takeEvery function analysis
The object processed by takeEvery is shown below
Important property: @@redux-saga/IO, which continues when true to perform the corresponding processing method based on the type of the object in this case “FORK”. Fn, the method to be executed note: the takeEvery in fn is not the same as the takeEvery used by rootSaga, which is key in saga and will be explained later
RootSaga internal takeEvery snippet (in IO -helpers.js)
function takeEvery(patternOrChannel, worker, ... args) {
//patternOrChannel is action.type, and worker is a user-defined generator function
return fork(takeEveryHelper/* takeEvery, */patternOrChannel, worker, ... args) }Copy the code
Fork source code and function description
The fork method is defined in IO. Js as follows:
function fork(fnDescriptor, ... args) {
return makeEffect(effectTypes.FORK, getFnCallDescriptor(fnDescriptor, args)/* { context, fn=takeEvery.js, args } */)}Copy the code
Look at the first getFnCallDescriptor:
function getFnCallDescriptor(fnDescriptor/*takeEveryHelper*/,
args/*patternOrChannel is both INCREMENT_ASYNC in rootSaga, worker is both incrementAsync in user defined saga method, if args is longer than 2, The third argument is a custom argument passed to 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 is the takeEvery method */ defined by takeevery.js,args/*['INCREMENT_ASYNC',incrementAsync] }
}
Copy the code
Something that makeEffect does
Let’s look at makeEffect again:
const makeEffect = (type, payload /* is the object returned by getFnCallDescriptor above */) = > ({
[IO]: true.combinator: false,
type,
payload,
})
Copy the code
From this we know the different types ofeffect
Will eventually pass throughmakeEffect
Process and return objects in this diagram.
Return to the code snippet for the third case: Effect is the data returned in the figure above
else if (effect && effect[IO]) {
const effectRunner = effectRunnerMap[effect.type]
effectRunner(env, effect.payload, currCb, executingContext)// If the take type is used, the execution is temporarily terminated until the dispatch occurs
}
Copy the code
Look at the content of the effectRunnerMap code (in 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,
}
Copy the code
We know effect.type=”FORK”, so effectRunner=runForkEffect.
RunForkEffect source code and functional analysis
Take a look at the runForkEffect internal code snippet:
/ / in effectRunnerMap. Js
function runForkEffect(env, { context, fn /* If takeEvery is used, it is the iterator created by takeevery.js */, args, detached }, cb, { task: parent }) {
const taskIterator = createTaskIterator({ context, fn, args })//args is action.type and custom generator
immediately(() = > {
const child = proc(env, taskIterator, parent.context, currentEffectId, meta, detached, undefined)})// Fork effects are non cancellables
}
Copy the code
CreateTaskIterator function parsing
The createTaskIterator method creates an iterator to be passed to the proc method below, which, as described above, causes the iterator passed to it to perform the iteration. Here is the createTaskIterator method fragment:
/ / in effectRunnerMap. Js
function createTaskIterator({ context, fn/*takeEvery*/, args/* ['INCREMENT_ASYNC',incrementAsync]*/}) {
const result = fn.apply(context, args)
// If it is an iterator, return it directly
if (is.iterator(result)) {
return result
}
let resolved = false
// The next method that emulates the ES6 iterator returns the same format as the es6Next method returns {value,done}
const next = arg= > {
if(! resolved) { resolved =true
// Only promises returned from fork are processed, and we can see that the iterator is not iterated if the promise value is returned
return { value: result, done: !is.promise(result) }
} else {
return { value: arg, done: true}}}return makeIterator(next)
}
Copy the code
Saga-helper takeEvery source code and function analysis, saga focus!
Let’s first look at the code for takeEvery:
function takeEvery(patternOrChannel /* action.type */, worker /* Custom generator */. 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}) `)},Copy the code
{@@redux-saga/IO: true, combinator: false, type: ‘TAKE’, payload:{pattern: The patternOrChannel}} process is essentially the same as the effect processing described above: wrap the incoming value, and effectRunner does the targeted processing to see what the take does
function take(patternOrChannel = The '*', multicastPattern) {
// As with the takeEvery Effect procedure above, makeEffect is called to wrap. Take stores the action. Type in the matching field (redux), as described below
return makeEffect(effectTypes.TAKE, { channel: patternOrChannel })
}
Copy the code
FsmIterator function parsing
FsmIterator method snippet:
// defined in 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 = qEndthrow error
} else {
stateUpdater && stateUpdater(arg)
constcurrentState = 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)
}
Copy the code
MakeIterator code:
function makeIterator(next, thro = kThrow, name = 'iterator') {
const iterator = { meta: { name }, next, throw: thro, return: kReturn, isSagaIterator: true }
return iterator
}
Copy the code
So what we’re returning is an iterator that simulates an iterator and has a next method inside it, which we can use as an iterator to go back to the createTaskIterator method
if (is.iterator(result)) {
return result
}
Copy the code
Is. iterator the logic for checking whether an iterator is an iterator is whether there are next and throw methods. The iterator simulated above has its own custom next and throw methods, so it passes and returns directly. Back to runForkEffect
function runForkEffect(env, { context, fn /* If takeEvery is used, it is the iterator created by takeevery.js */, args, detached }, cb, { task: parent }) {
const taskIterator = createTaskIterator({ context, fn, args })//args is action.type and custom generator
immediately(() = > {
const child = proc(env, taskIterator, parent.context, currentEffectId, meta, detached, undefined)})// Fork effects are non cancellables
}
Copy the code
The takeEvery simulation iterator in saga-Helper is made using fsmIterator. The key point is its internal next method
The taskIterator is now an iterator emulated in saga’s source code. When passed into proc methods, it executes its own custom next method:
// in the method fsmIterator
/* FSM is {q1() {return {nextState: 'q2', effect: yTake, stateUpdater: setAction}}, q2() {return {nextState: 'q1', effect: yFork(action) } }*/
/*nextState The startState passed in is assigned to 'q1'*/
function next(arg, error) {
if (nextState === qEnd) {
return done(arg)
}
if(error && ! errorState) { nextState = qEndthrow error
} else {
stateUpdater && stateUpdater(arg)
constcurrentState = error ? fsm[errorState](error) : fsm[nextState]() ; ({ nextState, effect, stateUpdater, errorState } = currentState)return nextState === qEnd ? done(arg) : effect
}
}
Copy the code
Focus on this line of codeconst currentState = error ? fsm[errorState](error) : fsm[nextState]()
No error executionfsm['q1']()
, from which return{ nextState: 'q2', effect: yTake, stateUpdater: setAction }
Deconstruct currentState to get Effect, which isyTake
, its value is shown in the figure below:The proc method does two main things: the iterator performs an iteration and the iterator processes the value returned by the iterator. The internal code is called digestEffect.
It’s time to go back to the above four-case process and execute the third of the four cases
// In the runEffect code inside the proc method
else if (effect && effect[IO]) {
const effectRunner = effectRunnerMap[effect.type]
effectRunner(env, effect.payload, currCb, executingContext)// If the take type is used, the execution is temporarily terminated until the dispatch occurs
}
Copy the code
Which effect. The type type to ‘take’, perform runTakeEffect, runTakeEffect code
Parsing the runTakeEffect method code and its functionality
function runTakeEffect(env, { channel = env.channel, pattern, maybe }, cb) {
//cb wraps the proc inner effect method's inner next method
// There is another layer of packaging for 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) /* The pattern of the custom saga will match the action. Type of the following dispatch */ : null)}catch (err) {
cb(err, true)
return
}
cb.cancel = takeCb.cancel
}
Copy the code
Another important module of Saga channel function analysis
Channel. take(takeCb, is. NotUndef (pattern)? Matcher (Pattern) : NULL) This line of code channel is the default argument when saga is first used, and this parameter is passed down
function sagaMiddlewareFactory({ context = {}, channel = stdChannel(), sagaMonitor, ... options } = {}) {... }Copy the code
A channel is the result of executing stdChannel(). Look at the stdChannel code
/ / in the channel. In js
function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input= > {
if (input[SAGA_ACTION]) {
put(input)
return
}
asap(() = > {
put(input)
})
}
return chan
}
Copy the code
It is not hard to see that channels are mainly the result returned by multicastChannel(). Look at the multicastChannel() code snippet, focusing on cb[MATCH] = matcher in the take method
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 is the wrapped proc internal next method
cb[MATCH] = matcher
ensureCanMutateNextTakers()
nextTakers.push(cb)// Store the callback to the task stack
cb.cancel = once(() = > {
ensureCanMutateNextTakers()
remove(nextTakers, cb)
})
},
close,
}
}
Copy the code
Channel. take(takeCb, is. NotUndef (pattern)? Matcher (pattern) : null) we know that matcher is matcher(pattern) where pattern is now ‘INCREMENT_ASYNC’
Redux-saga /effect takeEvery first entry to participate in action. Type comparison key code
Matcher method code snippet:
const string = pattern= > input= > input.type === String(pattern)
Copy the code
Since pattern is a string, we execute the above code. The method returned saves the pattern value as a closure, waits for input, compares it with input.type, and returns the result. So cb[MATCH] = Matcher defines the MATCH attribute assignment in the CB method. NextTakers. Push (CB) then stores this CB into an array inside the channel, at which point the take function completes. At the same time, our method call stack is inside the Proc method runEffect effectRunner(runTakeEffect)–>channel.take and when the take is done, it goes back to the Proc code and we see from the source that when the effectRunner is done, The proc code also completes.
The sagamiddleware. run(rootSaga) execution ends.
Saga brief implementation process:
Began to dispatch
Since the Saga middleware has been integrated into redux, what actually happens when you call dispatch is:
The code is in middleware.js
// The dispatch method has the format action=>action, which can be used as 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
}
Copy the code
Const result = Next (Action) performs dispatch when calling takeEvery if your reducers has the following code
function* rootSaga() {
yield takeEvery('INCREMENT_ASYNC', incrementAsync)
}
// The first argument to the takeEvery method is the same as the action. Type of the reduce response
function reducer(state = 0, action) {
switch (action.type) {
case 'INCREMENT_ASYNC':
console.log('Haha caught')
return state
default:
return state
}
}
Copy the code
Add channel function description, and parse channel.put function
Next comes the key point channel.put, where we know that a channel is generated by stdChannel
function stdChannel() {
const chan = multicastChannel()
const { put } = chan
chan.put = input= > {
if (input[SAGA_ACTION]) {
put(input)
return
}
asap(() = > {
put(input)
})
}
return chan
}
Copy the code
StdChannel simply wraps up multicastChannel’s PUT method; we just have to look at the put method inside multicastChannel
// The complete code in 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 saves the next wrapper for the next task
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,
}
}
Copy the code
Channel.put key code parsing of key functions
Back to the user saga code :action(‘INCREMENT_ASYNC’) actually returns {type:’INCREMENT_ASYNC’} as mentioned above, dispatch actually executes channel.put(action). The value of the action is both {type:’INCREMENT_ASYNC’}, so the input passed to multicastChannel.put is this action. Let’s focus on the following code
const takers = (currentTakers = nextTakers)//cb saves the next wrapper for the next task
for (let i = 0, len = takers.length; i < len; i++) {
const taker = takers[i]
if (taker[MATCH](input)) {
taker.cancel()
taker(input)
}
}
Copy the code
NextTaker the nextTaker array is stored into the proc next method when take is executed, and the Proc holds the iterator required by Next, so when PUT is executed, the iteration paused in take continues. Now scroll up to see what Taker is and how he figured it out. Briefly, taker[MATCH] stores the first argument in the yield takeEvery(‘INCREMENT_ASYNC’, incrementAsync)takeEvery method in the user rootSaga code. When a user initiates an action, taker(input) is executed using action.type against it.
Which iterator is taker executing at this time? Remember q1, q2? This is the iterator emulated in the Saga source code, which is made by fsmIterator in takeevery.js. YTake in Q1 finally calls channel.take to store the match and suspends iterator execution. When the user initiates an action and continues executing the iterator, the next iteration state of Q1 is Q2. Const yFork = ac => ({done: false, value: fork(worker, worker)… The args, ac)}).
Focus on the fork method, which takes worker (a user-defined generator function, in this case incrementAsync) as its input parameter, args as the parameter that needs to be passed to the generator function, and ac as the action that the user dispatches. The IO fork method is described in the sagamiddleware. run(rootSaga) procedure (see fork). The third of the four cases is then reached during next execution in proc, Digest effect, which actually extracts the worker stored in effect (in this case a user-defined incrementAsync generator function). Execute createTaskIterator to return an iterator. This method mainly considers the case of asynchronous promises. If a promise is returned, it is considered that the iteration has not been completed, and we know that the promise’s subsequent results will be executed in THEN). The iterator is then passed into the Proc to perform the iteration, and the process is repeated as the user’s custom generator function incrementAsync completes the iteration and the related methods begin to exit the call stack. Since q2 is returned, the next state of the simulation iterator will be Q1, and channel.take will be executed again, saving the MATCHED field method in CB [MATCHED]. (if you are confused about this description, please refer to the key code in redux-saga/effect to takeEvery first entry to participate in the comparison of action.type.)
The neat thing is that Q1, Q2 in takeEvery (other SagaHelpers have similar code, except takeLatest has Q3), which Saga relies on to run. It acts like a bow and arrow, pulling when the user sagamiddleware.run (rootSaga), firing when the user initiates an action, and then pulling again, waiting to fire.
Put method source, function analysis
In using the sample code
function* incrementAsync() {
yield delay(1000)
yield put({ type: 'INCREMENT'})}Copy the code
The PUT here actually calls the Dispatch that Saga passed in as redux middleware
function put(channel /* Action */, action) {
return makeEffect(effectTypes.PUT, { channel, action })
}
Copy the code
Effect is still returned, so look directly at the code in 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 returns action
result = (channel ? channel.put : env.dispatch)(action)
//channel may be an action that is dispatched but is assigned to the action parameter, so channal is assigned undefined
} catch (error) {
cb(error, true)
return
}
if (resolve && is.promise(result)) {
resolvePromise(result, cb)
} else {
cb(result)
}
})
Copy the code
Notice this line result = (channel? Channel. put: env.dispatch)(Action) whereas env.dispatch is set by channel
boundRunSaga = runSaga.bind(null, {
...options,
context,
channel,
dispatch,
getState,
sagaMonitor,
})
Copy the code
This is when you bind the incoming user, sagamiddleware.run (rootSaga). Thus the basic process of saga source code analysis ended. The next post will cover some details about Saga and how other effects work.