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的这种实现的两个好处:

    • 并行优化
    • 错误捕获机制
    • 写起来很漂亮
  • 相关阅读:
    ACM ICPC 2008–2009 NEERC MSC A, B, C, G, L
    POJ 1088 滑雪 DP
    UVA 11584 最短回文串划分 DP
    POJ 2531 Network Saboteur DFS+剪枝
    UVa 10739 String to Palindrome 字符串dp
    UVa 11151 Longest Palindrome 字符串dp
    UVa 10154 Weights and Measures dp 降维
    UVa 10271 Chopsticks dp
    UVa 10617 Again Palindrome 字符串dp
    UVa 10651 Pebble Solitaire 状态压缩 dp
  • 原文地址:https://www.cnblogs.com/dh-dh/p/7475083.html
Copyright © 2011-2022 走看看