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:

  1. Yield promise, effect is a promise
  2. Yield *generator() effect is an iterator
  3. Yield Effect (func) returns the value marked by saga effect where IO=@@redux-saga/IO property is true
  4. 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/awaitThe 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 ofeffectWill eventually pass throughmakeEffectProcess 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.