背景
为什么要给fetch增加拦截器?浏览器提供的fetch方法不够用吗?
是的。原生fetch确实不够用。
在项目中,如果想要在所有的网络请求之前往header中加入权限信息(比如:authorization=xxxx)。用原生fetch的话,你只能在每个fetch请求的时候,header配置中写上authorization=xxxx。或者在请求结果返回来之后,对响应结果做一些特殊处理,在原生fetch中,我们只能在每个请求的结果回来之后,都写一遍特殊处理。这样做起来一点都不优雅,一点都不高级。相信每一个程序员都是一个懒人,能用少量代码实现出更优雅,更健壮程序的话,绝对不用大量代码实现一个很脆弱的程序。
用过axios的人,都知道axios有拦截器功能(axios.interceptors)。遇到需要在所有ajax请求之前或者请求完成之后做一些事情的话,我们就可以在拦截器中写。拦截器可以在发起请求之前拦截请求,对请求做一些处理,然后,再继续请求;也可以在请求完成之后拦截请求,对响应结果做一些处理,然后再把结果返回。
这样做的好处是我们不需要在每个请求的时候,都去写相同的这些代码,可以把全局生效/通用的处理放在拦截器中来实现。可以省去很多代码,也避免了我们在开发时,不小心某一个请求漏掉了做全局处理。
拦截器设计思考
- 目的是在原生fetch基础之上增加拦截请求的功能。
- 封装后,最终暴露在外面的接口应该于原生fetch的使用方法一致,同时增加拦截器功能
- 拦截器的API设计参考axios的拦截器(个人比较喜欢axios的拦截器设计)。
拦截器API使用方法
你可以在then和catch之前拦截请求和响应。
// 添加一个请求拦截器
c_fetch.interceptors.request.use(function (config) {
// Do something before request is sent
return config;
});
// 添加一个响应拦截器
c_fetch.interceptors.response.use(function (response) {
// Do something with response data
return response;
});
拦截器实现方案
- 为了保证封装后的fetch暴露在最外面的api跟原生fetch保持一致。所以,我们要暴露到最外面的api应该是个跟fetch接收相同参数的函数。
我们看下原生fetch方法语法解释和接收参数解释。
语法:
Promise<Response> fetch(input[, init]);
参数:
?input
定义要获取的资源。这可能是:
一个 USVString 字符串,包含要获取资源的 URL。一些浏览器会接受 blob: 和 data: 作为 schemes.
一个 Request 对象。
init 可选
一个配置项对象,包括所有对请求的设置。可选的参数有:
method: 请求使用的方法,如 GET、POST。
headers: 请求的头信息,形式为 Headers 的对象或包含 ByteString 值的对象字面量。
body: 请求的 body 信息:可能是一个 Blob、BufferSource、FormData、URLSearchParams 或者 USVString 对象。注意 GET 或 HEAD 方法的请求不能包含 body 信息。
mode: 请求的模式,如 cors、 no-cors 或者 same-origin。
credentials: 请求的 credentials,如 omit、same-origin 或者 include。为了在当前域名内自动发送 cookie , 必须提供这个选项, 从 Chrome 50 开始, 这个属性也可以接受 FederatedCredential 实例或是一个 PasswordCredential 实例。
cache: 请求的 cache 模式: default 、 no-store 、 reload 、 no-cache 、 force-cache 或者 only-if-cached 。
redirect: 可用的 redirect 模式: follow (自动重定向), error (如果产生重定向将自动终止并且抛出一个错误), 或者 manual (手动处理重定向). 在Chrome中,Chrome 47之前的默认值是 follow,从 Chrome 47开始是 manual。
referrer: 一个 USVString 可以是 no-referrer、client或一个 URL。默认是 client。
referrerPolicy: Specifies the value of the referer HTTP header. May be one of no-referrer、 no-referrer-when-downgrade、 origin、 origin-when-cross-origin、 unsafe-url 。
integrity: 包括请求的 subresource integrity 值 ( 例如: sha256-BpfBw7ivV8q2jLiT13fxDYAe2tJllusRSZ273h2nFSE=)。
原生fetch的详细文档请移步MDN查阅:
developer.mozilla.org/zh-CN/docs/…
从上面的fetch的语法解释和参数解释,我们可以知道fetch是个函数;接收两个参数,第一个参数定义要获取的资源,第二个参数为可选项,一个配置项对象,包括所有对请求的设置;fetch函数分返回结果是一个promise对象。
- 我们先来实现封装后,暴露出来的c_fetch函数。
function c_fetch (input, init = {}) {
//fetch默认请求方式设为GET
if(!init.method){
init.method = 'GET'
}
//interceptors_req是拦截请求的拦截处理函数集合
//后面会讲解interceptors_req的定义与实现
interceptors_req.forEach(interceptors => {
init = interceptors(init);
})
//在原生fetch外面封装一个promise,为了在promise里面可以对fetch请求的结果做拦截处理。
//同时,保证c_fetch函数返回的结果是个promise对象。
return new Promise(function (resolve, reject) {
//发起fetch请求,fetch请求的形参是接收上层函数的形参
fetch(input, init).then(res => {
//interceptors_res是拦截响应结果的拦截处理函数集合
//后面会讲解interceptors_res的定义与实现
interceptors_res.forEach(interceptors => {
//拦截器对响应结果做处理,把处理后的结果返回给响应结果。
res = interceptors(res);
})
//将拦截器处理后的响应结果resolve出去
resolve(res)
}).catch(err => {
reject(err);
})
})
}
- c_fetch 函数实现完了,我们就差拦截器的实现了。我们在 c_fetch 函数的基础上增加interceptors,用来注册拦截器。
//定义用来存储拦截请求和拦截响应结果的处理函数集合
let interceptors_req = [], interceptors_res = [];
function c_fetch (input, init = {}) {
//fetch默认请求方式设为GET
if(!init.method){
init.method = 'GET'
}
//interceptors_req是拦截请求的拦截处理函数集合
interceptors_req.forEach(interceptors => {
init = interceptors(init);
})
//在原生fetch外面封装一个promise,为了在promise里面可以对fetch请求的结果做拦截处理。
//同时,保证c_fetch函数返回的结果是个promise对象。
return new Promise(function (resolve, reject) {
//发起fetch请求,fetch请求的形参是接收上层函数的形参
fetch(input, init).then(res => {
//interceptors_res是拦截响应结果的拦截处理函数集合
interceptors_res.forEach(interceptors => {
//拦截器对响应结果做处理,把处理后的结果返回给响应结果。
res = interceptors(res);
})
//将拦截器处理后的响应结果resolve出去
resolve(res)
}).catch(err => {
reject(err);
})
})
}
//在c_fetch函数上面增加拦截器interceptors,拦截器提供request和response两种拦截器功能。
//可以通过request和response的use方法来绑定两种拦截器的处理函数。
//use方法接收一个参数,参数为一个callback函数,callback函数用来作为拦截器的处理函数;
//request.use方法会把callback放在interceptors_req中,等待执行。
//response.use方法会把callback放在interceptors_res中,等待执行。
//拦截器的处理函数callback接收一个参数。
//request拦截器的callback接收的是请求发起前的config;
//response拦截器的callback接收的是网络请求的response结果。
c_fetch.interceptors = {
request: {
use: function (callback) {
interceptors_req.push(callback);
}
},
response: {
use: function (callback) {
interceptors_res.push(callback);
}
}
}
- 最后将整个封装之后,含有拦截器功能的fetch包装为一个插件暴露给开发者使用。
/**
* c_fetch
* 基于原生fetch封装了拦截器功能,暴露出来的c_fetch跟原生fetch用法一致,只是增加了拦截器功能。拦截器用法参考axios的拦截器用法。
* 拦截器: c_fetch.interceptors
* 注意: 拦截器不拦截reject类型的response结果
*/
//定义用来存储拦截请求和拦截响应结果的处理函数集合
let interceptors_req = [], interceptors_res = [];
function c_fetch (input, init = {}) {
//fetch默认请求方式设为GET
if(!init.method){
init.method = 'GET'
}
//interceptors_req是拦截请求的拦截处理函数集合
interceptors_req.forEach(interceptors => {
init = interceptors(init);
})
//在原生fetch外面封装一个promise,为了在promise里面可以对fetch请求的结果做拦截处理。
//同时,保证c_fetch函数返回的结果是个promise对象。
return new Promise(function (resolve, reject) {
//发起fetch请求,fetch请求的形参是接收上层函数的形参
fetch(input, init).then(res => {
//interceptors_res是拦截响应结果的拦截处理函数集合
interceptors_res.forEach(interceptors => {
//拦截器对响应结果做处理,把处理后的结果返回给响应结果。
res = interceptors(res);
})
//将拦截器处理后的响应结果resolve出去
resolve(res)
}).catch(err => {
reject(err);
})
})
}
//在c_fetch函数上面增加拦截器interceptors,拦截器提供request和response两种拦截器功能。
//可以通过request和response的use方法来绑定两种拦截器的处理函数。
//use方法接收一个参数,参数为一个callback函数,callback函数用来作为拦截器的处理函数;
//request.use方法会把callback放在interceptors_req中,等待执行。
//response.use方法会把callback放在interceptors_res中,等待执行。
//拦截器的处理函数callback接收一个参数。
//request拦截器的callback接收的是请求发起前的config;
//response拦截器的callback接收的是网络请求的response结果。
c_fetch.interceptors = {
request: {
use: function (callback) {
interceptors_req.push(callback);
}
},
response: {
use: function (callback) {
interceptors_res.push(callback);
}
}
}
export default c_fetch;
总结
本篇文章只是实现了一个最基本的拦截器功能,文章字数有限,没有深入讲解更加成熟的拦截器实现方式。有兴趣的朋友可以阅读下axios的拦截器实现,很有意思的。axios的拦截器会更加完善。
我这里针对fetch的封装也只是最基本的封装,目的是讲解拦截器的实现,没有过于复杂化。上文中暴露出来的c_fetch其实可以在封装一层cc_fetch,用bind方法把c_fetch的方法绑定在cc_fetch上,最后暴露出来的是cc_fetch。这样做的好处是保护了c_fetch的方法不会被外部所影响,篡改等。当然了,这只是我个人的一些看法,不代表所有人。
最后谢谢各位能够坚持阅读到最后,希望您阅读本篇文章能够有所收获。谢谢🙏~