ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

umi-request 中间件和拦截器解析

2020-11-24 15:33:09  阅读:547  来源: 互联网

标签:use 拦截器 请求 中间件 middleware request next fn


前言

使用react框架中经常使用到umi来搭建管理我们的项目,其中涉及到请求的模块,umi自身提供了 umi-request 库 方便了我们做网络请求, umi-request的官方文档可见其githubREADME.MD文件, 大部分功能都在readme中查询,大部分功能已经将的很清楚了,单独拿出 middlewareinterceptors 这两个概念讲一下。

为什么是 middlewareinterceptors

在使用umi-request的过程中最常见的需求是,对网络请求的拦截,在请求前或请求后做一些事情,例如URL添加前缀,过滤无效参数,上报接口错误,页面错误统一处理,单独接口定制化处理等..., middleware (中间件) 和 interceptors (拦截器) 以及 errorHandler 是实现此类需求绕不过去的概念,虽然他们都能影响请求和返回结果,但是哪种场景下使用哪个函数?需要了解其实如何实现的,以及代码的前后执行逻辑。

核心代码

  // onion/index.js
  execute(params = null) {
    const fn = compose([
      ...this.middlewares,
      ...this.defaultMiddlewares,
      ...Onion.globalMiddlewares,
      ...Onion.coreMiddlewares,
    ]);
    return fn(params);
  }
 
  1. middlewares 和 interceptors.response 的关系 伪代码就是 middleware(ctx) ctx 对象中包含responseInterceptors函数 ,也就是说responseInterceptors函数 会作为 middleware的参数使用
  2. compose函数会把middlewares中的函数按照顺序串联成一个可以递归调用的函数,fn(params) 会把这个递归调用函数执行掉

关于middleware

  • 使用

    • request.use(fn) // 默认加载到 onion实例上
    • request.use(fn, { defaultInstance: true })
    • request.use(fn, { global: true })
    • request.use(fn, { core: true })
  • 调用逻辑:extend(configObj)生成request实例,request初始化中生成一个Core实例,Core实例初始化中使用一个Onion实例, 我们调用的request.use实际上是Onion实例上的use方法

  • use方法的第二个参数是可选参数,根据这个配置决定这个middleware加载到哪一层,调用的顺序也是不一样的,具体查看官方给的示例,

  • request.use(fn) 默认加载到 onion实例上,执行的时候会首先进入这一类的middleware

  • request.use(fn, { defaultInstance: true }) 官方给出的描述是 “默认实例中间件,供开发者使用”,此类middleware还是会加载到Onion实例上,默认Core实例中给出了"[]"的配置

  • request.use(fn, { global: true }) ,全局中间件中本身就内置了三个函数 [simplePost, simpleGet, parseResponseMiddleware]

    • simplePost实现了默认请求头设置, 'Content-Type': 'application/json;charset=UTF-8' 自动配置form类型的请求头; 功能在next()前;
    • simpleGet实现了get请求使用params参数自动序列化;功能在next()前;
    • parseResponseMiddleware实现了Http Status Code 的判断 copy.status >= 200 && copy.status < 300 之外的code会作为错误被抛出; 功能在next()之后;
    • 自己加入的middleware会放在这三个后,执行的顺序用洋葱模型理解一下就是“后进先出”
  • request.use(fn, { core: true }), 核心中间件内置了一个函数 [fetchMiddleware], 也是umi-request真正发起接口调用的地方,使用的是浏览器原生的fetch对象

  • fetchMiddleware 实现了浏览器兼容性判断,缓存数据获取,超时逻辑,取消请求功能;在原生fetch请求回的第一时间,就进入了responseInterceptors的函数,所以interceptors.response.use进来的拦截器函数是第一批接触到真正fetch返回的response对象的函数,此时的response 对象是没有经过包装的原生fetch请求回来的对象,对象上常用的属性有status, statusText, ok等属性

关于Interceptors 拦截器

  • Interceptor分为两部分,请求前的拦截,请求后的拦截
  • 请求前的拦截,是在进入洋葱模型之前执行的函数,也就是说interceptors.request.use中的函数,会比任何middleware中的函数执行的早
   return new Promise((resolve, reject) => {
      this.dealRequestInterceptors(obj) // 在此处完成了interceptors.request.use中函数的执行
        .then(() => onion.execute(obj))
        .then(() => {
          resolve(obj.res);
        })
        .catch(error => {
        ...
    });

  • 请求前的拦截可以操作的属性或对象只有 url 和 options 两个,只对这样个对象进行操作
  • 请求后的拦截是在 onion洋葱模型的 最深处 执行,也就是上面提到的fetchMiddleware在获取到fetch response 对象的第一时间会交给interceptors.response.use中的拦截函数处理

errorHandler

  • errorHandler执行的时机最晚,在各种拦截器各种中间件执行完成以后,这个时候如果捕获到了异常才执行。
  • 默认的errorHandler的逻辑是,如果有配置errorHandler,errorHandler垫底,如果没有配置errorhandle会直接抛出异常;
  • 单个请求option的 errorHandler 会覆盖 extend(config) 中的 config.errorHandler, 这属于option覆盖的逻辑,不展开细讲,有不懂的可以看一下官方文档或源码

middleware 和 interceptor 可以拿到的参数

  • 先看下ctx 对象
 const obj = {
      req: { url, options },
      res: null,
      cache: this.mapCache,
      responseInterceptors: [...Core.responseInterceptors, ...this.instanceResponseInterceptors],
    };
  • middleware(ctx, next) 函数 next 表示进入到下一层(里面一层),next 前的逻辑和next后的逻辑,按照洋葱的进出的定义来理解,ctx 就是上述ctx对象
  • requestIntercetor的参数是 url 和options配置
  • responseIntercetor的参数是 原生的fetch产生的res对象

最后结合官方的示例理解一下执行顺序

request.use(async (ctx, next) => {
  console.log('instanceA1');
  await next();
  console.log('instanceA2');
});
request.use(async (ctx, next) => {
  console.log('instanceB1');
  await next();
  console.log('instanceB2');
});
request.use(
  async (ctx, next) => {
    console.log('globalA1');
    await next();
    console.log('globalA2');
  },
  { global: true }
);
request.use(
  async (ctx, next) => {
    console.log('coreA1');
    await next();
    console.log('coreA2');
  },
  { core: true }
);

// 执行顺序是
instanceA1 -> instanceB1 -> globalA1 -> coreA1 -> coreA2 -> globalA2 -> instanceB2 -> instanceA2

再加上我们上面的理解可以理解成这样

// 看一下fetch的位置
instanceA1 -> instanceB1 -> globalA1 -> coreA1 -> fetch() -> coreA2 -> globalA2 -> instanceB2 -> instanceA2

// 看一下内置中间件 + 拦截器的执行顺序 
requestInterceptor -> 自定义middleware -> simplePost-> simpleGet-> parseResponseMiddleware -> fetch -> responseInterceptor 
自定义middleware <- simplePost <- simpleGet <- parseResponseMiddleware  <-

写在最后

虽然没有具体规定哪个函数应该做什么,但是知道执行顺序以后,就很方便我们拓展了

标签:use,拦截器,请求,中间件,middleware,request,next,fn
来源: https://www.cnblogs.com/wuhaozhou/p/14030540.html

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有