zoukankan      html  css  js  c++  java
  • 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的这种实现的两个好处:

    • 并行优化
    • 错误捕获机制
    • 写起来很漂亮
  • 相关阅读:
    用TortoiseSVN忽略文件或文件夹(ignore)(网络摘抄记录)
    GridView解决同一行item的高度不一样,如何同一行统一高度问题?
    解决android studio引用远程仓库下载慢(转)
    Databinding在自定义ViewGroup中如何绑定view
    (转 )【Android那些高逼格的写法】InvocationHandler与代理模式
    (转)秒懂,Java 注解 (Annotation)你可以这样学
    View拖拽 自定义绑定view拖拽的工具类
    bcrypt对密码加密的一些认识(学习笔记)
    Node.js+Koa开发微信公众号个人笔记(三)响应文本
    Node.js+Koa开发微信公众号个人笔记(二)响应事件
  • 原文地址:https://www.cnblogs.com/dh-dh/p/7475083.html
Copyright © 2011-2022 走看看