Express基于Connect

connect is a middleware layer for Node.js

express中间件的基本用法

const connect = require('connect');
const http = require('http');

const app = connect();

// 回调函数的第一个参数不是String类型, 默认为'/'根目录
app.use((req, res, next) => {
  console.log('middleware 1')
  next();
});
app.use((req, res, next) => {
  console.log('middleware 2')
  next();
});

app.use('/foo', (req, res, next) =>  {
  console.log('middleware /foo')
  res.end('我是 /foo');
  next();
});
app.use('/bar', (req, res, next) =>  {
  console.log('middleware /bar')
  res.end('我是 /bar');
  next();
});

//create node.js http server and listen on port
http.createServer(app).listen(3000);

启动服务, 本地访问 http://localhost:3000/

// 服务端
middleware 1
middleware 2

访问http://localhost:3000/bar`

// 服务端
middleware 1
middleware 2
middleware /bar

repl.it在线DEMO

express中间件实现--代码实现

看connetct源码前, 先尝试自己实现类似的功能, 当做衬托大神的绿叶😁

// like-express.js
const http = require("http");
const slice = Array.prototype.slice;

class Express {
  constructor() {
    this.stacks = [];
  }
  _isMatch(req, stack) {
    if (req.url === "/favicon.ico") {
      return false;
    }
    if (req.url.indexOf(stack.path) === 0) {
      if (req.method.toLowerCase() === stack.method || stack.method === "all") {
        return true;
      }
    }
    return false;
  }
  _resgister(method, args) {
    if (typeof args[0] === "string") {
      this.stacks.push({
        path: args[0],
        middleware: slice.call(args, 1),
        method
      });
    } else {
      this.stacks.push({
        path: "/",
        middleware: slice.call.call(args),
        method
      });
    }
  }
  use() {
    this._resgister("all", arguments);
  }
  get() {
    this._resgister("get", arguments);
  }
  post() {
    this._resgister("post", arguments);
  }
  _trigger(req, res) {
    const finalStacks = [];
    this.stacks.forEach(stack => {
      if (this._isMatch(req, stack)) {
        finalStacks.push(...stack.middleware);
      }
    });
    const next = () => {
      const cb = finalStacks.shift();
      if (cb) {
        cb(req, res, next);
      }
    };
    next();
  }
  _serverHandle(req, res) {
    res.json = data => {
      res.setHeader("content-type", "application/json");
      res.end(JSON.stringify(data));
    };
    this._trigger(req, res);
  }
  listen() {
    // 利用箭头函数在定义时绑定
    const server = http.createServer((req, res) => {
      // console.log("this === http", this === http); // false
      // console.log("this intanceof Express", this instanceof Express); // true
      this._serverHandle(req, res);
    });
    server.listen(...arguments);
  }
}

module.exports = function() {
  return new Express();
};

index.js文件如下,

// index.js

const expressLike = require("./like-express");
const app = expressLike();

app.use("/", (req, res, next) => {
  console.log(1, "use 开始");
  next();
  console.log(1, "use 结束");
  res.json({
    say: "Hello World"
  });
});
app.get("/test", (req, res, next) => {
  console.log(2, "get 开始");
  next();
  console.log(2, "get 结束");
});
app.post("/test", (req, res, next) => {
  console.log(3, "post 开始");
  next();
  console.log(3, "post 结束");
});
app.use("/test", (req, res, next) => {
  console.log(4, "use 开始");
  next();
  console.log(4, "use 结束");
});
app.listen("1314", () => {
  console.log("server is running on PORT 1314");
});

node index.js, 然后浏览器访问http://localhost:1314/test, 或 curl http://localhost:1314/test

1 'use 开始'
2 'get 开始'
4 'use 开始'
4 'use 结束'
2 'get 结束'
1 'use 结束'

connect v3.7.0代码分析

代码分析的方式是在源码基础上作注释, 请看☞ gist (TODO: 源码分析注释)

Koa基于koa-compose

Koa的洋葱圈模型

image-20190718115958165

Koa1的中间件使用方式

与express的中间件相比, 中间件支持回调函数为Generator 函数

Koa2中间件使用方式

与Koa1相比, 中间件的回调函数支持了更友好的 async 函数

分析koa-compose

compose , 有一个API compose([a, b, c, ...]) , 返回中间件

index.js中的源码为: (TODO: 源码分析注释)

'use strict'

/**
 * Expose compositor.
 */

module.exports = compose

/**
 * Compose `middleware` returning
 * a fully valid middleware comprised
 * of all those which are passed.
 *
 * @param {Array} middleware
 * @return {Function}
 * @api public
 */

// 假设
// middleware = [
//   async (ctx, next) => {
//     console.log(1)
//     await next()
//   }
// ]

function compose (middleware) {
  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]
      if (i === middleware.length) fn = next
      if (!fn) return Promise.resolve()
      try {
        // 每个中间件都是一个 generator or async , 接收 context 和 next 两个参数
        // 每个中间件调用都会在 next调用处卡住知道递归执行下一个 dispatch ,取出下一个中间件
        // 这样只有后面的中间件的 dispatch resolve掉,前面的中间件才会继续执行,最外层的 dispatch(0) 才会 resolve 掉
        return Promise.resolve(fn(context, dispatch.bind(null, i + 1)));
      } catch (err) {
        return Promise.reject(err)
      }
    }
  }
}

Redux基于compose (属于函数式编程)

基本原理 : 属于函数式编程中的组合,它将chain中的所有匿名函数[f1,f2,f3,…,fn]组成一个新的函数,即新的dispatch,假设n = 3: dispatch = f1(f2(f3(store.dispatch)))这时调用新dispatch,每一个middleware就依次执行了。

redux基本用法

import { createStore, applyMiddleware } from 'redux'

const middleware1 = store => next => action => {
  console.log('before1')
  next(action)
  console.log('after1')
}

const middleware2 = store => next => action => {
  console.log('before2')
  next(action)
  console.log('after2')
}

function reducer(state, action) {
  if (action.type === '1' || action.type === '2') {
    return {
      ...state,
      name: state.name + action.payload
    }
  }
  return state
}

// store = applyMiddleware(middleware1, middleware2)(createStore)(reducer, {name: 'xu'})

// 另一种用法, 因为createStore内部有enhancer判断,applyMiddleware 返回的就是一个enhancer

const enhancer = applyMiddleware(middleware1, middleware2)
store = createStore(reducer, { name: 'xu' }, enhancer)
// 等价于上一条语句,可读性更好, 这就是函数柯里化的好处

store.dispatch({
  type: '1',
  payload: '-1'
})

console.log(store.getState())

store.dispatch({
  type: '2',
  payload: '-2'
})

console.log(store.getState())

Redux的中间件是介入action与reducer之间, 原理图

image-20190718130517362

关键源码如下:
image-20190718121729175

分析applyMiddleware.js


import compose from './compose'

/**
 * Creates a store enhancer that applies middleware to the dispatch method
 * of the Redux store. This is handy for a variety of tasks, such as expressing
 * asynchronous actions in a concise manner, or logging every action payload.
 *
 * See `redux-thunk` package as an example of the Redux middleware.
 *
 * Because middleware is potentially asynchronous, this should be the first
 * store enhancer in the composition chain.
 *
 * Note that each middleware will be given the `dispatch` and `getState` functions
 * as named arguments.
 *
 * @param {...Function} middlewares The middleware chain to be applied.
 * @returns {Function} A store enhancer applying the middleware.
 */
export default function applyMiddleware(...middlewares) {
  return createStore => (...args) => {
    const store = createStore(...args)
    let dispatch = () => {
      throw new Error(
        'Dispatching while constructing your middleware is not allowed. ' +
          'Other middleware would not be applied to this dispatch.'
      )
    }

    const middlewareAPI = {
      getState: store.getState,
      // 这里很关键,也很巧妙! 没有使用store.dispatch 而是用了一个 `dispatch` 变量,下面会被compose的dispatch覆盖,
      // 这样传入 middlware 的第一个参数中的 dispatch 即为覆盖后的dispatch, 
      // 对于类似 redux-thunk 这样的中间件,内部会调用 'store.dispatch', 使得其同样会走一遍所有中间件
      dispatch: (...args) => dispatch(...args)
    }
    
    // 应用中间件的第一层参数, 为了给中间件暴露store的getState和dispatch方法
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    
    // compose 带来的就是剥洋葱似的函数调用 compose(f, g, h) => (...args) => f(g(h(...args)))
    // redux 中间件的核心就是复写 dispatch
    // 把 store.dispatch 传递给第一个中间件
    // 每一个中间件都会返回一个新的 dispatch 传递给下一个中间件
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

分析compose.js

/**
 * Composes single-argument functions from right to left. The rightmost
 * function can take multiple arguments as it provides the signature for
 * the resulting composite function.
 *
 * @param {...Function} funcs The functions to compose.
 * @returns {Function} A function obtained by composing the argument functions
 * from right to left. For example, compose(f, g, h) is identical to doing
 * (...args) => f(g(h(...args))).
 */

/**
 * compose(f, g, h) => (...args) => f(g(h(...args)))
 */
export default function compose(...funcs) {
  if (funcs.length === 0) {
    return arg => arg
  }

  if (funcs.length === 1) {
    return funcs[0]
  }

  return funcs.reduce((a, b) => (...args) => a(b(...args)))
}

总结一下三者的区别

express

  1. 中间件为一个方法,接受 req,res,next三个参数。
  2. 中间可以执行任何方法包括异步方法。
  3. 最后一定要通过res.end或者next来通知结束这个中间件方法。
  4. 如果没有执行res.end或者next访问会一直卡着不动直到超时。
  5. 并且在这之后的中间件也会没法执行到。

koa

  1. 中间件为一个方法或者其它,接受ctx,next两个参数。
  2. 方法中可以执行任何同步方法。可以使用返回一个Promise来做异步。
  3. 中间件通过方法结束时的返回来判断是否进入下一个中间件。
  4. 返回一个Promise对象koa会等待异步通知完成。then中可以返回next()来跳转到下一个中间件。
  5. 如果Promise没有异步通知也会卡住。

Redux

  1. 中间件为一个方法,接受store参数。
  2. 中间可以执行任何方法包括异步方法。
  3. 中间件通过组合串联middlware。
  4. 通过next(action)处理和传递action直到redux原生的dispatch,或者使用store.dispatch(actio)来分发action。
  5. 如果一只简单粗暴调用store.dispatch(action),就会形成无限循环。

本文由 givencui 创作,采用 知识共享署名 3.0,可自由转载、引用,但需署名作者且注明文章出处。

楼主残忍的关闭了评论