基于whatwg-fetch+typescript封装带拦截器的"fetch"

3,574 阅读4分钟

原生fetch是不带类似于axios的interceptors的,最近公司的项目中使用的是whatwg-fetch库,就想着手动实现封装一下这个功能

为什么使用拦截器?

我们的业务中经常需要全局的对请求进行处理,比如添加api前缀、添加鉴权token数据、全局对request,response数据进行处理,我们不可能在每次请求的时候对每一个请求单独处理,这样的工作量在十来个接口调用的时候看着没关系,如果大型项目,好几十上百的接口的时候,难道一个一个这样去修改吗? 而且一旦需求有所变动,将带来致命的打击,一不小心bug就出来了。因此使用拦截器对请求进行处理尤为关键。

什么是拦截器?

由上一条所说的业务需求我们可以分析出拦截器就是在请求、响应之前调用对数据进行处理然后在返回的中间过程,这么一说,其实拦截器跟我们经常所提到的中间件是一样的概念。

  • 举个例子

加入我们需要在header中添加token数据,然后在header中添加一个特殊标识,最后让浏览器用这个header去后端访问接口我们该怎么做?

let options = {
    method: 'GET',
    headers: {
      "content-type": "application/x-www-form-urlencoded"
    },
    // ...省略其他
}

// k1 -----------------------------此处作为标记

let http = fetch('/user' options);
http.then(s=>{
    return s.json()
}).then(res=>{
    console.log(res)
})

以上是一个fetch发送一个http请求的过程,怎么实现我们说的在header中添加token呢?

function resolveOptions(options){
    options.headers.token = 'beerxddrgdfg24556xxxxxx'
    return options
}
function resolveOptions1(options){
    options.headers.a = '2'
    return options
}

如果把上述两个方法放在k1标识处执行一下,是不是就可以达到我们的效果?答案是肯定的 此时resolveOptions,resolveOptions1就是两个拦截器。通常我们拦截器都是链式执行的,也就是说我们的options需要经过一个一个的拦截器中间过程处理完才能进入fetch中进行请求。这是对request的处理,response处理是同样的道理。我们的拦截器的大致思路就有了:在请求前后对request对象以及response对象进行处理。可能会有多个拦截器的,因此需要维护一个队列,让拦截器依次执行,同时也要考虑异步的情况。很自然的想到了koa2中间件的的洋葱圈模型,我的一片文章中也有分析过。 传送门-Koa中间件原理, 强烈建议先看这一部分才能更好的理解下面的代码

实现对myFetch的封装

我们期望像koa一样使用中间件的方式去使用 myFetch

设想:let myFetch = new MyFetch()
// 请求拦截器
myFetch.useRequest_interceptors(async (request,next)=>{
    request.a = 1
    await next()
})
// 响应拦截器
myFetch.useResponse_interceptors(async (response,next)=>{
    response.data.b = {a:1,b:2}
    await next()
})
myFetch.fetch('/user',{id:1}).then(res=>{
    console.log(res)
    // 此时的res已经被响应拦截器处理过了
})

实现myFetch基类

interface InterceptorFn {
  (payload: any, next: Function): any
}
class FetchAPI implements FetchAPIType {
  fetch: (url: string, options: any) => Promise<any>;
  request_interceptors: Array<InterceptorFn> = [];
  response_interceptors: Array<InterceptorFn> = [];
  constructor() {
    this.fetch = this.fetchMethod
  }
  // 定义Request拦截器
  useRequestInterceptors(fn: InterceptorFn) {
    this.request_interceptors.push(fn);
  }
  // 定义response拦截器
  useResponseInterceptors(fn: InterceptorFn) {
    this.response_interceptors.push(fn);
  }
  fetchMethod = (url: string, options: any) => {
    return fetchPro(
      url,
      options,
      this.request_interceptors,
      this.response_interceptors
    );
  }
}

实现fetchMethod

 const fetchPro: fetchPro = (
 url,initOptions = {},req_interceptors,res_interceptors
) => {
  initOptions.url = url;
  initOptions.mode = "cors";
  if (req_interceptors.length > 0) {
  // 在这个地方组合所有的request拦截器,目的是让他依次执行
    composeInterceptors(req_interceptors, initOptions)();
  }
  return new Promise((resolve, reject) => {
    const URL = initOptions.url;
    delete initOptions.url;
    console.log('promise-methods')
    //发起fetch请求,fetch请求的形参是接收上层函数的形参
    fetch(URL, initOptions).then((res: any) => res.json()).then((result: any) => {
      if (res_interceptors.length > 0) {
       // 在这个地方组合所有的response拦截器,目的是让他依次执行
        let fn = composeInterceptors(res_interceptors, result);
        fn()
      }
      //将拦截器处理后的响应结果resolve出去
      resolve(result);
    })
      .catch((err: any) => {
        reject(err);
      });
  });
};

实现compose拦截器队列



interface InterceptorFn {
  (payload: any, next: Function): any
}
interface fetchPro {
  (url: string, initOptions: any, req_interceptors: Array<InterceptorFn>, res_interceptors: Array<InterceptorFn>): Promise<any>
}
interface ComposeInterceptors {
  (Interceptor: Array<InterceptorFn>, payload: any): any
}


const createNextInterceptor: Function = (Interceptor: InterceptorFn, next: InterceptorFn, payload: any) => {
  return async function () {
    return await Interceptor(payload, next);
  };
};
// 组合Interceptors
const composeInterceptors: ComposeInterceptors = (Interceptor, payload) => {
//对所有的拦截器进行reduce归一化处理,执行下一个中间件的方法被包裹起来作为参数传递为next
  return Interceptor.reduceRight(
    (pre, cur) => {
      return createNextInterceptor(cur, pre, payload);
    },
    result => {
      Promise.resolve();
    }
  );
};

结语

由于项目使用了whatwg-fetch,我们使用typescript的时候request与response的类型没有定义因此使用了any类型,去npm查看whatwg-fetch的 types 竟然已经被移除了,暂未找到,所以对中间件中的request与response的代码提示以及校验做的很不够,暂时没找到解决方案。以后作为优化点继续优化。