• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

理解前端的 Middleware 原理和实现

武飞扬头像
薛定谔的猫96
帮助2

学新通

前言

最早提出 Middleware 概念的是 Express,随后由原班人马打造的 Koa 不但沿用了 Middleware 的架构设计,还更加彻底的把自己定义为中间件框架。Redux也引入了 Middleware 的概念,方便独立功能的函数对 Action 进行处理。Axios虽然没有中间件,但其拦截器的用法却跟中间件十分相似。本文结合使用场景,拆解对比各大框架的 Middleware 的实现原理, 。

学新通

Middleware

Middleware(中间件)本意是指位于服务器的操作系统之上,管理计算资源和网络通信的一种通用独立的系统软件服务程序。分布式应用软件借助这种软件在不同的技术之间共享资源。

而大前端领域,Middleware 一般指提供通用独立功能的数据处理函数,包括日志记录、数据叠加和错误处理等。

Express

Express 中应用层级的中间件的注册方式:

  1.  
    const stack = [];
  2.  
    /** 通过 use 注册 */
  3.  
    function use(fn) {
  4.  
    stack.push(fn);
  5.  
    }
  6.  
     
  7.  
    /** 请求到达的时候,会触发handle方法。接着next函数从队列中顺序取出并执行 */
  8.  
    function handle(req, res) {
  9.  
    var idx = 0;
  10.  
    next();
  11.  
    function next() {
  12.  
    var fn = stack[idx ];
  13.  
    fn(req, res, next)
  14.  
    }
  15.  
    }
学新通

Koa

Koa的 Middleware 注册跟路由无关,所有的请求都会经过注册的中间件。同时Koa 支持async/await异步编程模式:

  1.  
    /** 注册 */
  2.  
    function use(fn) {
  3.  
    // 省略部分代码...
  4.  
    this.middleware.push(fn);
  5.  
    return this;
  6.  
    }

Koa 的 Middleware 顺序执行,通过 dispatch函数来控制,compose 函数对已注册的中间件列表(middleware)栈内每一个中间件函数的校验,并返回 fn  函数。

  1.  
    function compose (middleware) {
  2.  
    if (!Array.isArray(middleware)) throw new TypeError('Middleware stack must be an array!');
  3.  
    for (const fn of middleware) {
  4.  
    if (typeof fn !== 'function') throw new TypeError('Middleware must be composed of functions!');
  5.  
    }
  6.  
     
  7.  
    /**
  8.  
    * @param {Object} ctx
  9.  
    * @return {Promise}
  10.  
    * @api public
  11.  
    */
  12.  
    return function fn (ctx, next) {
  13.  
    return dispatch(0);
  14.  
    function dispatch (i) {
  15.  
    let middlewareFn = middleware[i]
  16.  
    try {
  17.  
    return Promise.resolve(middlewareFn(ctx, dispatch.bind(null, i 1)));
  18.  
    } catch (err) {
  19.  
    return Promise.reject(err);
  20.  
    }
  21.  
    }
  22.  
    }
  23.  
    }
学新通

Redux

Redux中间件的参数经过柯里化,store是applyMiddleware内部传进来的,next是compose后传进来的,action是dispatch传进来的

  1.  
    export default function applyMiddleware(...middlewares) {
  2.  
    return (createStore) =>(reducer, preloadedState) => {
  3.  
    const store = createStore(reducer, preloadedState)
  4.  
    let dispatch = store.dispatch;
  5.  
    let chain = [];
  6.  
    const middlewareAPI = {
  7.  
    getState: store.getState,
  8.  
    dispatch: (action) => dispatch(action)
  9.  
    }
  10.  
    /** 先执行一遍middleware,把第一个参数store传进去 */
  11.  
    chain = middlewares.map(middleware => middleware(middlewareAPI));
  12.  
    /** 传入原始的dispatch */
  13.  
    dispatch = compose(...chain)(store.dispatch)
  14.  
    return {
  15.  
    ...store,
  16.  
    dispatch
  17.  
    }
  18.  
    }
  19.  
    }
学新通

这里 compose 的返回值又重新赋值给dispatch:

  1.  
    function compose (...funcs) {
  2.  
    if (funcs.length === 0) {
  3.  
    return arg => arg
  4.  
    }
  5.  
    if (funcs.length === 1) {
  6.  
    return funcs[0]
  7.  
    }
  8.  
    return funcs.reduce((a, b) =>(...args) => a(b(...args)))
  9.  
    }

说明我们在应用内调用的dispatch并不是store自带的,而是经过 Middleware 处理的升级版。

Axios

axios没有中间件,但有类似功能的拦截器(interceptors),本质上都是在数据处理链路的 2 点之间,提供独立的、配置化的、可叠加的额外功能。

  1.  
    function Axios(instanceConfig) {
  2.  
    this.defaults = instanceConfig;
  3.  
    this.interceptors = {
  4.  
    request: new InterceptorManager(),
  5.  
    response: new InterceptorManager()
  6.  
    };
  7.  
    }
  8.  
    function InterceptorManager() {
  9.  
    this.handlers = [];
  10.  
    }
  11.  
    InterceptorManager.prototype.use = function use(fulfilled, rejected) {
  12.  
    this.handlers.push({
  13.  
    fulfilled: fulfilled,
  14.  
    rejected: rejected
  15.  
    });
  16.  
    return this.handlers.length - 1;
  17.  
    };
学新通

Axios内部会维护 2 个 interceptors,它们有独立的 handlers 数组。use就是往数组添加元素而已,跟其它框架不同的是这里的数组元素不是一个函数,而是一个对象,包含fulfilled和rejected 2 个属性。第二个参数不传的时候rejected就是 undefined。

通过 promise 的链式调用,将 interceptors 串联了起来,执行顺序是:requestInterceptorChain -> chain -> responseInterceptorChain。这里有一个默认的约定,chain 里的元素都是按照[fulfilled1, rejected1, fulfilled2, rejected2]这种模式排列的,所以注册 interceptors 的时候如果没有提供第二个参数,也会有一个默认值 undefined:

  1.  
    Axios.prototype.request = function request(config) {
  2.  
    config = mergeConfig(this.defaults, config);
  3.  
    // 成对的添加元素
  4.  
    var requestInterceptorChain = [];
  5.  
    this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) {
  6.  
    requestInterceptorChain.unshift(interceptor.fulfilled, interceptor.rejected);
  7.  
    });
  8.  
     
  9.  
    var responseInterceptorChain = [];
  10.  
    this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) {
  11.  
    responseInterceptorChain.push(interceptor.fulfilled, interceptor.rejected);
  12.  
    });
  13.  
     
  14.  
    var chain = [dispatchRequest, undefined];
  15.  
     
  16.  
    Array.prototype.unshift.apply(chain, requestInterceptorChain);
  17.  
    chain.concat(responseInterceptorChain);
  18.  
     
  19.  
    promise = Promise.resolve(config);
  20.  
    while (chain.length) {
  21.  
    promise = promise.then(chain.shift(), chain.shift());
  22.  
    }
  23.  
    return promise;
  24.  
    }
学新通

总结

学新通

这里面最精妙也是最难理解的就是Array.reduce这种形式,需要反复的推敲。promise.then链式调用的任务编排方法也十分巧妙,前面处理完的数据会自动传给下一个then。递归调用的形式则最好理解,Koa在Express实现的基础上天然支持异步调用,更符合服务器端场景 

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanhiaegcj
系列文章
更多 icon
同类精品
更多 icon
继续加载