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
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的洋葱圈模型
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之间, 原理图
关键源码如下:
分析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
- 中间件为一个方法,接受 req,res,next三个参数。
- 中间可以执行任何方法包括异步方法。
- 最后一定要通过res.end或者next来通知结束这个中间件方法。
- 如果没有执行res.end或者next访问会一直卡着不动直到超时。
- 并且在这之后的中间件也会没法执行到。
koa
- 中间件为一个方法或者其它,接受ctx,next两个参数。
- 方法中可以执行任何同步方法。可以使用返回一个Promise来做异步。
- 中间件通过方法结束时的返回来判断是否进入下一个中间件。
- 返回一个Promise对象koa会等待异步通知完成。then中可以返回next()来跳转到下一个中间件。
- 如果Promise没有异步通知也会卡住。
Redux
- 中间件为一个方法,接受store参数。
- 中间可以执行任何方法包括异步方法。
- 中间件通过组合串联middlware。
- 通过next(action)处理和传递action直到redux原生的dispatch,或者使用store.dispatch(actio)来分发action。
- 如果一只简单粗暴调用store.dispatch(action),就会形成无限循环。
楼主残忍的关闭了评论