Reading the title, you might wonder why redux is such a different framework than Koa and Express, so why make the analogy? Middleware such as Express and KOA, however, refers to code that can be embedded in the framework to receive requests and generate responses. Redux’s Middleware provides extension points after actions are initiated and before they reach the Reducer. I think, no matter what frame it is, an idea is the most important. The same concept may be realized by different ideas and methods, and it is also worth studying and learning.

Facing various business scenarios, the front and back ends need a plug-in mechanism that can be combined at will. Middleware is a mechanism that allows you to combine, plug and remove widgets. So let’s take a horizontal look at how Middleware works in Redux, KOA, and Express. Here are three ways to do this:

  1. Asynchronous programming mode
  2. The use of middleware
  3. The implementation principle and implementation of Middleware

1. Asynchronous programming mode

Understanding asynchronous programming is a prerequisite for understanding how middleware works.

The framework asynchronous
Express callback
Koa1 generator/yield+co
Koa2 Async/Await
Redux Story – thunk, redux – saga, redux – promise, etc
  • Express: Since it was pre-ES6, the middleware is still based on callback
  • Koa: KOA1 benefits from generator features and adds automatic process management through the CO framework. (CO encapsulates all generator returns as Promise objects.) KOA2 uses Async/Await (just the syntactic sugar of genertator functions)
  • Redux: The asynchronous redux becomes an asynchronous action. There are several ways to solve asynchronous actions, such as redux-thunk,redux-saga, redux-Promise, etc

This article does not cover the details of asynchronous programming in JS. We recommend ruan Yifeng to write a series of articles entitled “Learn ECMAScript 6 Asynchronous Programming in Depth”.

Use of Middleware

Koa, as we all know, is an agile development framework that the original Express team has redeveloped based on new ES6 features. Express is primarily based on the Connect middleware framework, which encapsulates a lot of functionality in itself, such as routing and requests. While Koa is based on CO (KOA2 is based on async/await) middleware framework, the framework itself does not integrate too many functions, most functions need to be solved by the user require middleware. Let’s start by comparing Koa with Express:

//Express var express = require('express') var app = express() app.get('/',(req,res)=>{ res.send('Hello Express! ') }) app.listen(3000)Copy the code

//Koa
var koa = require('koa')
var app = koa()
var route = require('koa-route')
app.use(route.get('/',async (ctx) => {
    ctx.body = 'Hello Koa'
}))
app.listen(3000)Copy the code

Redux expands with middleware that includes custom functionality. Middleware lets you wrap a store’s dispatch method to do what you want. Middleware also has a key feature called “compose.” (More on this later) More than one middleware can be combined to form a chain. Each middleware doesn’t need to know anything about the middleware before or after it in the chain.

Such as:

const logger = ()=>{
    // ...
}
const crashReporter = ()=>{
    // ...
}
const thunk = () =>{
    // ...
}
let store = createStore(
  App,
  applyMiddleware(
    crashReporter,
    thunk,
    logger
  )
)Copy the code

3. Implementation principle and implementation of Middleware

1. Implementation principle and implementation of Middleware for Express

Express is more like sequential execution of middleware, called a linear model

Left -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | middleware1 | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- left -- -- -- -- -- -- -- -- -- -- -- -- -- -- - |... . . | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- left -- -- -- -- -- -- -- -- -- -- -- -- -- -- - | middlewareN | -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- leftCopy the code

Express middleware maintains an array of functions that need to be executed before a response is sent. After each use, the middleware is pushed into the array. After execution, the next method is called to execute the next function of the function. If no call is made, the call terminates. Let’s implement a simple Express middleware feature

Function express() {var funcs = [] var app = function (req, Function next() {var task = funcs[I ++] if (! Task) {// If the middleware does not exist,return return} task(req, res, next); App.use = function (task) {funcs.push(task); funcs.push(task); } return app // return instance}Copy the code

2. Implementation principle and implementation of MIDDLEWARE of KOA

Unlike Express, which pushes multiple middleware onto the stack, Koa’s middleware is a so-called onion model.

Koa’s middleware implementation relies primarily on KoA-Compose. The koA-compose module can combine multiple middleware components into one:

const Koa = require('koa')
const compose = require('koa-compose')
const app = new Koa()
const logger = (ctx, next) => {
  console.log(`${Date.now()} ${ctx.request.method} ${ctx.request.url}`)
  next()
}
const main = ctx => {
  ctx.response.body = 'Hello Koa'
};
const middlewares = compose([logger, main])
app.use(middlewares)
app.listen(3000)Copy the code

Let’s take a look at koa-compose’s source code

module.exports = compose /** * Compose `middleware` returning * a fully valid middleware comprised * of all those which Are passed. * * @param {Array} middleware * @return {Function} * @API public */ Function compose (middleware) {// error handling if (! Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array! ') for (const fn of middleware) { if (typeof fn ! == 'function') throw new TypeError('Middleware must be composed of functions! ') } /** * @param {Object} context * @return {Promise} * @api public */ return function (context, next) { // last called middleware # let index = -1 return dispatch(0) function dispatch (i) { if (i <= index) return Promise. Reject (new Error('next() called multiple times')) // Index = I let fn = middleware[I] // All middleware is complete  if (i === middleware.length) fn = next if (! Use (fn) return promise.resolve (fn(context, function next () { return dispatch(i + 1) })) } catch (err) { return Promise.reject(err) } } } }Copy the code

Koa’s middleware supports normal functions, functions that return a Promise, and async functions.

2. Implementation principle and implementation of Middleware of Redux

“It provides a third-party extension point between Dispatching an action,and the moment It reaches the reducer” This is how redux’s author describes it.

Because of redux’s single-source nature, data flows from the top, and middleware acts as a conduit to support that flow, with different conduits having different features and functions.

Each Middleware function takes the Store’s dispatch and getState functions as named arguments and returns a function. This function is passed into the next Middleware dispatch method called Next and returns a new function that receives an action, which can call Next (Action) directly, at any other time needed, or not call it at all. The last middleware in the call chain accepts the actual Store’s dispatch method as the next argument and ends the call chain.

Redux-thunk Middleware redux-Thunk Middleware redux-Thunk Middleware

Redux-thunk helps you unify the way asynchronous and synchronous actions are called, putting asynchronous processes at the action level

function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { if (typeof action === 'function') { return action(dispatch, getState, extraArgument);  } return next(action); }; } const thunk = createThunkMiddleware(); thunk.withExtraArgument = createThunkMiddleware; export default thunk;Copy the code

The action itself is an object with Type and arguments. Passing Dispatch and getState to the action above, next() and Action () are the methods redux provides. Next, if action is a function, return action(dispatch, getState,extraArgument), otherwise return next(action).

Then reference them to the Redux Store

import { createStore, combineReducers, applyMiddleware } from 'redux' let todoApp = combineReducers(reducers) let store = createStore( todoApp, // applyMiddleware() tells createStore() how to handle middleware applyMiddleware(thunk))Copy the code

Let’s start with the source code

import compose from './compose' export default function applyMiddleware(... middleware){ return (next) => (reducer,initialState) => { let store = next(reducer, initialState) let disptach = store.dispatch let chain = [] var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } chain = middlewares.map(middlre => middleware(middlewareAPI)) dispatch = compose(... chain)(store.dispatch) return { ... store, dispatch } } }Copy the code

Let’s take a look at redux’s Middleware mechanism from the following points.

  1. The idea of Functional programming redux Middlreware is a method of implementing multi-parameter functions using a hidden list of parameter functions.

ES6 implements a curring function


> function curring(fn){ > return function curried(... args){ > return args.length >= fn.length ? fn.call(this,... args):(... rest)=>{ > return curried.call(this,... args,... rest) > } > } > } >Copy the code

Benefits:

  • Easy in-line
  • Shared store
  1. Distribution of the store

Create a normal store

let newStore = applyMiddleware(mid1,mid2,...) (createStore)(reducer,null)Copy the code

Due to the

var middlewareAPI = {
            getState: store.getState,
            dispatch: (action) => dispatch(action)
        } 
chain = middlewares.map(middlre => middleware(middlewareAPI))Copy the code

Because of closures, each anonymous function can access the same store, the middlewareAPI.

  1. compose

    The source for compose is a function called compose:
    export default function compose(... Funcs) {if (funcs.length === 0) {return arg => arg} if (funcs.length === 1) {return funcs[0]} // Get the last function const last = funcs[funcs.length - 1]; // Get function [0,length-1) const rest = funcs.slice(0, Curried return (...args) => rest.reduceright ((composed, f) => f(composed), last(...args))}Copy the code

Compose here is somewhat similar to koa-compose in the article. Is a combination in functional programming that combines all the anonymous functions in the chain [F1, F2, F3… fn] into a new function, the new Dispatch, assuming n = 3: Dispatch = F1 (f2(F3 (store.dispatch))) a new dispatch is called and each middleware is executed in turn.

  1. Call Dispatch in middleware



From the diagram above, Middleware layers and passes actions through next(Action) up to Redux’s native Dispatch. If one middleware uses Store.dispatch (Action) to distribute actions, it’s like starting all over again.

A typical scenario for using dispatch in Middleware is to accept a directed action that doesn’t want to reach the native distribution action. This is often used in one-step requests, such as redux-Thunk, which accepts dispatch directly.

Let’s summarize the differences

express

  1. Middleware is a method that takes req, RES, and next.
  2. Any method can be executed in between, including asynchronous methods.
  3. Be sure to end the middleware method by notifying res.end or next.
  4. If res.end or next is not executed, access will remain stuck until time out.
  5. And after that the middleware will not be able to execute.

koa

  1. The middleware accepts CTX and next as parameters for a method or another.
  2. Any synchronized method can be executed in the. You can do asynchrony by returning a Promise.
  3. The middleware determines whether to proceed to the next middleware by returning at the end of the method.
  4. Return a Promise object and KOA waits for the asynchronous notification to complete. Then returns next() to jump to the next middleware.
  5. Promises can also get stuck if they don’t have asynchronous notifications.

Redux

  1. Middleware is a method that takes the store argument.
  2. Any method can be executed in between, including asynchronous methods.
  3. Middleware is connected by a combination of middlware.
  4. Next (Action) processes and delivers the action up to Redux’s native Dispatch, or uses Store.dispatch (Actio) to distribute the action.
  5. If a simple call to store.dispatch(action) is made, an infinite loop is created.

Reference documentation