The source code for both Redux and Koa’s middleware mechanisms is minimal.

In the text I will directly quote some of the source code and annotate it to help us understand the middleware mechanism more clearly.

Reudx

The middleware mechanism of Redux in the source code mainly involves two modules

The inside of thecomposeCombination function

'redux/src/compose.js'One thing to note in advance is that functions in the funcs array are basically (injected into the API) middleware we'll add in the future like Logger,thunk, etcexport default functioncompose(... Funcs) {// To ensure consistent output, always return a functionif (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    returnFuncs [0]} // This step may be a bit abstract, but the code is extremely refined, processing an array through a reduction function that ultimately returns a layered self-calling composite function. // example: compose(f, g, h) returns (... args) => f(g(h(... args))).returnfuncs.reduce((a, b) => (... args) => a(b(... Args)))} // Perhaps due to a version update, compose is much simpler than the one we saw previously, especially in the final specification function handling. // Middleware evolved from reduce sequential execution to function call, which is more functional. The compose function is an old one, so you can compare it.export default functioncompose(... funcs) {if (funcs.length === 0) {
    return arg => arg
  }
  if (funcs.length === 1) {
    return funcs[0]
  }
  const last = funcs[funcs.length - 1]
  const rest = funcs.slice(0, -1)
  return(... args) => rest.reduceRight((composed, f) => f(composed), last(... args)) }Copy the code

Redux provides the middleware we most commonly use when adding middlewareapplyMiddlewarefunction

'redux/src/applyMiddleware.js'// You can see that the compose function imports compose from as 🔧'./compose'// Exposed to developers for passing in middlewareexport default functionapplyMiddleware(... Middlewares) {// Control of the createStore function is transferred to applyMiddleware, which I won't extend because this article is about middlewarereturncreateStore => (... The args) = > {/ / -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- the associated middleware, Some context code -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / initializes the store, here... Reducer, preloadedState(optional) const store = createStore(... Args) // Declare a zero-hour dispatch function, note herelet, which will be replaced after the build is completelet dispatch = () => {
     throw new Error('Dispatch is not allowed to be called at build time, in order to prevent user-defined middleware from calling Dispatch at initialization time. As you can see in the examples below, regular synchronous middleware typically doesn't use Dispatch.} // The API provided to the middleware function, you can see the dispatch function here via the function'Dynamically invoke dispatch in current environment'const middlewareAPI = { getState: store.getState, dispatch: (... args) => dispatch(... Const chain = middlewares. Map (Middleware => Middleware (middlewareAPI)) //---------------------------------------------------------------------------------------------------------------------- --'key'// The above example might be intuitive: the call to compose(f, g, h) returns (... args) => f(g(h(... Args)). //1. Call the compose function, which returns a composite function composed of multiple middleware components //2. Passing store.dispatch as an argument to the composite function returns a new/wrapped dispatch function //'Note: this part needs to be understood in relation to the middleware source code below, so let's put this in the mental cache for now.'dispatch = compose(... Chain)(store.dispatch) // returns a store object from which the actual store is fetched with middleware added.return {
      ...store,
      dispatch
    }
  }
}
Copy the code

Third-party middleware

I didn’t want to write this long, but I hope more people can understand more simple, I posted some source code, after all, the code is far better than the text to understand, I use logger and Thunk source code (simplified) to do a brief analysis of the above.

'redux-logger'// Since logger source code looks a bit complicated, I simply implemented it... Generally speaking, Redux's middleware has two main layers. // The first layer, which accepts the API provided by the Store, is called before it is passed to the build middleware. Const logger = ({getState}) => {// First, using a function (currying) that will run/delay, let me use more comments to help us clear things up... Compose (f, g, h) : compose(f, g, h) : compose(f, g, h) : args) => f(g(h(... Args)). // 表 示 : compose(... Chain)(store.dispatch) ((dispatch) => f(g(h(dispatch))(store.dispatch) Store.dispatch is passed as a parameter to the middleware that executes first (rightmost) // the second layer of the middleware that executes, returning one'Function that takes Action as an argument'This function acts as the middleware that calls the next one (to its left) and executes to the far left, eventually returning the same one'Function that accepts action'// The final dispatch we call is actually the function that is finally returned'Our real process is dispatch(wrapped) => middleware 1 => middleware 2 => Dispatch (provided by store) => middleware 2 => Middleware 1 => Assignment (if returned)'// Please look at the code several times instead of my commentsreturn next => action => {
        console.log('action', action)
        console.log('pre state', getState()) // Next is essentially the closure returned by the next(right) middleware // If the current middleware is the last or only one, then next is the store-provided dispatch // Next (Action) continues down the call stack, NextVal = next(action) console.log() const nextVal = next(action) console.log()'next state', getState()) // returns the result to the previous middleware (left) or to the developer (in the case of the first middleware)return nextVal
    }
}


'redux-thunk'This function supports the asynchronous operation of dispatch. Let's see how it is implemented. // The above comment will not be repeated here, but the support for asynchrony will be explained.function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => next => action => {
    if (typeof action === 'function') {// Transfer the execution authority of the dispatch function to the developer, we usually call Dispatch after the asynchrony ends (which is synchronous). // Note: here our original middleware execution process is interrupted and re-executed in synchronous mode,'So the position of the redux-thunk in the middleware will affect the rest of the middleware, such as logger middleware being executed twice or something... '// Another thing to note here is that the dispatch function is actually wrapped after middleware is built.returnaction(dispatch, getState, extraArgument); } // Dispatch transfers control directly to the next middleware when synchronizing. // Dispatch When asynchronous, control is also transferred to the next middleware in the dispatch that is invoked after the asynchron ends.return next(action);
  };
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;

Copy the code

summary

Finally, let’s review the middleware process for Redux.

  1. The first is to provide a compsoe function to generate a composite function composed of multiple middleware (with self-calling capability)
  2. Inject the Store API into the middleware
  3. Pass store.dispath as an argument to a compose function that returns a wrapped dispatch (the actual dispatch we use).
  4. (or 0.) build a specific structure of middleware, with the first layer used to inject apis and the second layer used to accept one returned by the previous middlewareA function that takes action as an argument, and also returns an action containing the middleware itselfA function that takes action as an argument.
  5. The dispatch provided by the middleware is called, and the middleware is called in turn. If asynchronous support is provided, the dispatch will be called in accordance with the ordinary process in the asynchronous case. When functions such as Redux-thunk or Redux-Promise are encountered, The current Dispatch is re-invoked synchronously (the middleware is also re-invoked)

Below is a painstakingly drawn call flow chart… Feel free to look around…

Koa2

I don’t think many friends see this… But I still have to finish it. As above, post the source code first and let the code tell us the truth

In Redux, middleware exists as an additional feature, but in KOA middleware is its primary mechanism.

Koa’s core code is scattered across multiple independent libraries, starting with the compose function at the heart of the middleware mechanism

'koa-compose'/ / note:'Always returns Promise in functions due to koA2's async await syntax sugar form'// Take an array of middlewarefunctionCompose (Middleware) {returns a handler that is called at the end of a Request and passes in the parameters associated with the Requestreturn function (context, next) {
    letIndex = -1 // executes and returns the first middlewarereturnDispatch (0) each takes a numeric parameter that is used to call the middleware in turnfunction dispatch (i) {
      if (i <= index) return Promise.reject(new Error('next() called multiple times'))
      index = i
      letFn = middleware[I] // Call itself directly if only one middleware is availableif(I === middleware.length) fn = next // The middleware is finished and returns a Promiseif(! fn)returnPromise.resolve() try {// wrap the next middleware function call in next and return it to the current middlewarereturn Promise.resolve(fn(context, function next () {
          returnDispatch (I + 1)})} Catch (err) {// Listen for an error and throw itreturnPromise.reject(err)}}}} // Redux's middleware lacked a bit of functional sophistication, but I still couldn't write anything like pared-down codeCopy the code

Koa ontology

'koa/lib/application.js'

'Line 104-115 use function (simplified)'Use (fn) {// Add middleware to the array this.middleware.push(fn);return this;
  }

'Lines 125-136 callback function'// Callback will be called in koa.listencallback() {// call compsoe const fn = compose(this.middleware);if(! this.listeners('error').length) this.on('error', this.onerror); const handleRequest = (req, res) => { const ctx = this.createContext(req, res); //Request is calledreturn this.handleRequest(ctx, fn);
    };
    return handleRequest;
  }

Copy the code

Koa2 summary

I didn’t explain much about the middleware mechanics of KOA2, mainly because it was much simpler than redux middleware, and also because it was lazy. The actual execution flowchart was actually the same onion shape, but the store.dispatch was replaced by the last middleware.

At the end of the

Although the quality of this article is not good, most of the notes are colloquial (lack of professional vocabulary), but I still hope to help some students.

Better retreat than netting