koa/redux middleware 深入解析
middleware
对于现有的一些框架比如koa,express,redux,都需要对数据流进行一些处理,比如koa,express的请求数据处理,包括json.stringify,logger,或者一些安全相关的处理都需要在数据流中进行,还比如redux的整个数据的修改,支持中间件来扩展用户对于数据修改的支持。
middleware系统是处理流式数据的利器,实现方便,功能强大。
本文就分别研究一下redux的koa的middleware系统~
redux
对于redux,一个数据处理中心,它的用法想必大家已经很熟了。
import thunk from "redux-thunk";
import {applyMiddleware,createStore} from "redux";
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore)
我们直接上redux源码:
function applyMiddleware() {
for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) { //跟compose一样处理参数
middlewares[_key] = arguments[_key];
}
return function (createStore) {
return function (reducer, initialState, enhancer) {
var store = createStore(reducer, initialState, enhancer);//传入createStore方法
var _dispatch = store.dispatch; //重要的dispatch方法
var chain = [];
var middlewareAPI = { //需要传入middleware的reduxAPI
getState: store.getState,
dispatch: function dispatch(action) {
return _dispatch(action);
}
};
chain = middlewares.map(function (middleware) { //遍历每个中间件把reduxAPI传进去。返回一个封装好的中间件
return middleware(middlewareAPI);
});
_dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch); //相当于compose(...chain)(store.dispatch)
return _extends({}, store, { //然后给store重写的这个执行完中间件的dispatch方法。
dispatch: _dispatch
});
};
};
}
我们这里只传了一个thunk中间件,当然也可以传多个middleware,可以看到源码里把applyMiddleware所有参数保存为中间件。
我们只讲重要的middleware应用部分。对于中间件的处理,redux重写了dispatch方法,在dispatch action的时候先经过一遍中间件,流式的通过中间件处理,再进行dispatch。核心代码就是 _dispatch = _compose2["default"].apply(undefined, chain)(store.dispatch);,其实就是compose(...chain)(store.dispatch)。
我们用thunk来具体讲一下流程。
thunk的源码是:
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;
实际我们引入过来在redux源码里的就是
({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
之间通过chain处理过middlmiddleware,所以chain里的各个middleware其实是这样的:
next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
根据compose的作用,我们实际获得的_dispatch是这样的:
如果只有一个middleware,我们传进来store.dispatch,那么_dispatch为:
action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return dispatch(action);
};
如果有两个middleware(我们实际在演示compose的处理),我们得到的其实是。。。之前我们先简化一下middleware:
function middleware(next){
return function(action){
return {
// before
next(action);
// xxx;
}
}
}
第二个middleware传进来我们得到的是:
function (){
return function(action){
return {
(function(action){
return {
// before
dispatch(action);
// xxx
}
})(action)
}
}
}
第三个middleware:
function (){
return function(action){
return {
(function(action){
//before 1
return (function(action){
return {
// before 2
dispatch(action);
// xxx 2
}
})(action)
// xxx 1
})(action)
}
}
}
我们会发现,_dispatch触发之后,需要经过各个自执行的中间件。
我们还会发现,我们的执行顺序是根据compose的执行顺序来的,但是在中间件调用之后并不会返回,我们还会执行next之后的‘xxx’代码。而它的代码顺序是相反的。
但是我们并不会在next之后执行命令阿?我们的规范也都是以next结尾。
原因?
next的后的代码其实是可以用的,但是有一点问题就是还是执行顺序的问题,如果某个中间件是异步执行,执行顺序就无法保证了。
这个情况一直持续到es6 generator函数的出现。。。
koa
对于koa,1.x的时候支持了generator,现在支持了async函数,所以现在的koa的中间件系统是“洋葱圈”式的处理方式,也就是上面的先执行每个中间件的before,再倒叙执行xxx函数。
有个图直观的感受一下:

所有的请求经过一个中间件的时候都会执行两次。
因为koa的每个middleware是无关的,所以我们并不需要像redux的compose一样用reduceRight实现,它的compose实现一会再谈,先谈一下middleware的工作流程吧。(redux的compose实现之前博文有讲)
const server = http.createServer(this.callback());
server.listen(...args);
const fn = compose(this.middleware);
const handleRequest = (req, res) => {
res.statusCode = 404;
const ctx = this.createContext(req, res);
const onerror = err => ctx.onerror(err);
const handleResponse = () => respond(ctx);
onFinished(res, onerror);
return fn(ctx).then(handleResponse).catch(onerror);
};
我们构建一个服务,请求来的时候传入一个上下文环境给中间件,然后请求通过中间件处理。
koa的中间件形式就是图上那种的格式,我这里只讲一下最新的async的处理模式吧,因为此源码是基于aysnc的处理。
举个middleware的例子:
async function middleware(ctx, next){
// before
await next();
// xxx;
}
app.use其实判断一下middleware然后push进this.middleware,没有什么好说的。
对于这种“洋葱圈”式的处理方式,主要就是koajs/compose处理的。
它是怎么做的呢? koajs/koa
function compose (middleware) {
//首先保证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 // 如果传了next,处理完所有middleware之后调用。
if (!fn) return Promise.resolve() // 如果为空或者调用完返回空的promise函数。
try {
return Promise.resolve(fn(context, function next () {
return dispatch(i + 1) // 尾递归调用下个middleware
}))
} catch (err) {
return Promise.reject(err)
}
}
}
}
这个compose比较简单,因为组件间的关联从返回值变成了context。koa之间中间件的联系应该就是一个全局通用的context参数了。也是koa推荐写法。
这个compose就是递归调用所有的middleware。
值得一提的点就是koa为async函数特制的compose函数,async函数的awiat需要每次异步都是一个promise,如果为值,那就是同步处理。所以返回的middleware都被包了一层Promise.resolve。
它的处理过程就是:
一个middleware:
async function middleware(ctx, next){
// before
await next();
// xxx;
}
两个middleware:
async function middleware(ctx, next){
// before
// before2
await next2();
// xxx2;
// xxx;
}
next之后的xxx函数的调用顺序保证得益于async的函数执行顺序。且把await看做then的语法糖。
错误处理机制
比较方便的一点就是在try里面,所有中间件的reject都会被catch到,这得益于与promise的一个特性:
如果resolve的参数是Promise对象,则该对象最终的[[PromiseValue]]会传递给外层Promise对象后续的then的onFulfilled/onRejected
// middleware/onerror.js
// global error handling for middlewares
module.exports = async (ctx, next) => {
try {
await next();
} catch (err) {
err.status = err.statusCode || err.status || 500;
let errBody = JSON.stringify({
code: -1,
data: err.message
});
ctx.body = errBody;
}
};
优势
middleware的好处就不提了。
只说一下koa的这种实现的两个好处:
- 并行优化
- 错误捕获机制
- 写起来很漂亮
koa/redux middleware 深入解析的更多相关文章
- koa/redux middleware系统解析
middleware 对于现有的一些框架比如koa,express,redux,都需要对数据流进行一些处理,比如koa,express的请求数据处理,包括json.stringify,logger,或 ...
- redux middleware 源码分析
原文链接 middleware 的由来 在业务中需要打印每一个 action 信息来调试,又或者希望 dispatch 或 reducer 拥有异步请求的功能.面对这些场景时,一个个修改 dispat ...
- redux middleware 的理解
前言 这几天看了redux middleware的运用与实现原理,写了一个百度搜索的demo,实现了类似redux-thunk和redux-logger中间件的功能. 项目地址:https://git ...
- redux源码解析-函数式编程
提到redux,会想到函数式编程.什么是函数式编程?是一种很奇妙的函数式的编程方法.你会感觉函数式编程这么简单,但是用起来却很方便很神奇. 在<functional javascript> ...
- redux源码解析(深度解析redux+异步demo)
redux源码解析 1.首先让我们看看都有哪些内容 2.让我们看看redux的流程图 Store:一个库,保存数据的地方,整个项目只有一个 创建store Redux提供 creatStore 函数来 ...
- 如何学习理解Redux Middleware
Redux中的middleware其实就像是给你提供一个在action发出到实际reducer执行之前处理一些事情的机会.可以允许我们添加自己的逻辑在这段当中.它提供的是位于 action 被发起之后 ...
- Redux Middleware All in One
Redux Middleware All in One https://redux.js.org/advanced/middleware https://redux.js.org/api/applym ...
- 再探Redux Middleware
前言 在初步了解Redux中间件演变过程之后,继续研究Redux如何将中间件结合.上次将中间件与redux硬结合在一起确实有些难看,现在就一起看看Redux如何加持中间件. 中间件执行过程 希望借助图 ...
- 初识Redux Middleware
前言 原先改变store是通过dispatch(action) = > reducer:那Redux的Middleware是什么呢?就是dispatch(action) = > reduc ...
随机推荐
- 使用node操作mongodb
let mongodb = require('mongodb'); let MongodbClient = mongodb.MongoClient; MongodbClient.connect('mo ...
- MySQL 的两个特殊属性 unsigned与 zerofill
1 unsigned unsigned 就是将数字类型无符号化, 例如 int 型的范围:-2^31 ~ 2^31 - 1,而unsigned int的范围:0 ~ 2^32.看起来unsigned ...
- [转帖]整理:Windows系统下的奇技淫巧大汇总
整理:Windows系统下的奇技淫巧大汇总 https://blog.csdn.net/bat67/article/details/76381357 Win+home Crtl+home 还有 Win ...
- bootstrap模态框关闭后清除模态框的数据
https://segmentfault.com/q/1010000008789123 bootstrap模态框第二次打开时如何清除之前的数据? 我用了bootstrap模态框的remote功能,在弹 ...
- Spring boot + mybatis + orcale实战(干货)
废话少说,直接上步骤: 第一步:安装好IDEA(此处省略) 第二步:在IDEA新建springboot工程 第三步:在springboot工程的pom.xml添加oracle和mybait依赖 < ...
- flutter image_picker使用照相机
dependencies: image_picker: ^0.4.12+1 最新的^0.5+9编译无法通过 import 'dart:io'; import 'dart:async'; import ...
- Flutter的scope_model使用mixin语法报错
在pubspec.yaml同级目录下创建analysis_options.yaml文件,内容: # https://www.dartlang.org/guides/language/analysis- ...
- CodeForces 126B Password
题目链接:http://codeforces.com/problemset/problem/126/B 题目大意: 多组数据每组给定1个字符串S,问是否存在S的一个尽量长的子串,同时是S的前缀和后缀, ...
- Protocol buffers--python 实践(二) protocol buffers vs json
为什么专门开一个坑,来使用pb.放弃本在各平台上都支持得很好的json而使用pb的一个归根到底的理由,就是希望在保证强类型和跨平台的情况下,能够更轻,更快,更简单.既然是奔着这个目标去的,到底多快我需 ...
- AI算法第一天【概述与数学初步】
1. 机器学习的定义: 机器从数据中学习出规律和模式,以应用在新数据上作出预测的任务 2.学习现象: (1)语言文字的认知识别 (2)图像,场景,物体的认知和识别 (3)规则:下雨天要带雨伞 (4)复 ...