原生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的代码提示以及校验做的很不够,暂时没找到解决方案。以后作为优化点继续优化。