当前位置 : 首页 > 车辆数据

Axios拦截器实现:Promise链式调用与请求响应处理

<|begin▁of▁sentence|># 1. 前言 在上一篇文章中,我们实现了`axios`的基础功能,但到目前为止,我们都只是实现了一个很简单的`axios`,一个完整的`axios`应该还有拦截器、配置化、取消请求等功能,那么从本篇文章开始,我们就来把这些功能一一实现。本篇文章先来实现拦截器功能。 # 2. 示例 我们先来看一下`axios`官方提供的拦截器使用示例: ```javascript // 添加请求拦截器 axios.interceptors.request.use( function(config) { // 在发送请求之前做些什么 return config; }, function(error) { // 对请求错误做些什么 return Promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( function(response) { // 对响应数据做点什么 return response; }, function(error) { // 对响应错误做点什么 return Promise.reject(error); } ); ``` 从示例中我们可以看出:`axios`对象上有一个`interceptors`对象属性,该属性又有两个属性`request`和`response`,它们都有一个`use`方法,`use`方法支持两个参数,第一个参数类似`Promise`的`resolve`函数,第二个参数类似`Promise`的`reject`函数。我们可以在`resolve`函数和`reject`函数中执行同步代码或者是异步代码逻辑。 并且,我们是可以添加多个拦截器的,拦截器的执行顺序是:对于请求拦截器,后添加的拦截器会在请求前的过程中先执行;对于响应拦截器,先添加的拦截器会在响应后先执行。 ```javascript // 添加请求拦截器1 axios.interceptors.request.use( function(config) { console.log("请求拦截器1"); return config; }, function(error) { return Promise.reject(error); } ); // 添加请求拦截器2 axios.interceptors.request.use( function(config) { console.log("请求拦截器2"); return config; }, function(error) { return Promise.reject(error); } ); // 添加响应拦截器1 axios.interceptors.response.use( function(response) { console.log("响应拦截器1"); return response; }, function(error) { return Promise.reject(error); } ); // 添加响应拦截器2 axios.interceptors.response.use( function(response) { console.log("响应拦截器2"); return response; }, function(error) { return Promise.reject(error); } ); ``` 在上述代码中,我们添加了两个请求拦截器和两个响应拦截器,那么当我们发起请求时,控制台的输出顺序应该是: ```javascript 请求拦截器2; 请求拦截器1; 响应拦截器1; 响应拦截器2; ``` OK,我们已经知道拦截器的使用了,接下来,我们就来一步一步实现它。 # 3. 实现思路 我们仔细思考一下:拦截器的本质是一个实现特定条件的`Promise`链式调用。请求拦截器,相当于在`Promise`链式调用的`dispatchRequest`之前添加;响应拦截器,相当于在`Promise`链式调用的`dispatchRequest`之后添加。如下: ```javascript // 伪代码 // 添加拦截器前 Promise.resolve(config) .then(dispatchRequest) .then((response) => { console.log(response); }); // 添加请求拦截器和响应拦截器后 let newPromise = Promise.resolve(config); // 遍历请求拦截器,插入到newPromise之后 interceptors.request.forEach((interceptor) => { newPromise = newPromise.then(interceptor.resolve, interceptor.reject); }); newPromise = newPromise.then(dispatchRequest); // 遍历响应拦截器,插入到newPromise之后 interceptors.response.forEach((interceptor) => { newPromise = newPromise.then(interceptor.resolve, interceptor.reject); }); newPromise.then((response) => { console.log(response); }); ``` 所以,我们实现的思路应该是: 1. 定义一个类`InterceptorManager`用于管理拦截器,该类上有一个`use`实例方法用于添加拦截器,并且该类还应有一个存储拦截器的数组; 2. 在`Axios`类上有一个`interceptors`属性,该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例; 3. 在发送请求时,先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中; # 4. 拦截器管理类 根据实现思路,我们先来定义一个拦截器管理类`InterceptorManager`,我们在`src`目录下创建`interceptorManager.ts`文件: ```typescript // src/interceptorManager.ts import { ResolvedFn, RejectedFn } from "../types"; interface Interceptor { resolved: ResolvedFn; rejected?: RejectedFn; } export default class InterceptorManager { private interceptors: Array | null>; constructor() { this.interceptors = []; } use(resolved: ResolvedFn, rejected?: RejectedFn): number { this.interceptors.push({ resolved, rejected, }); return this.interceptors.length - 1; } forEach(fn: (interceptor: Interceptor) => void): void { this.interceptors.forEach((interceptor) => { if (interceptor !== null) { fn(interceptor); } }); } eject(id: number): void { if (this.interceptors[id]) { this.interceptors[id] = null; } } } ``` 我们定义了一个`InterceptorManager`泛型类,内部维护了一个私有属性`interceptors`,它是一个数组,用来存储拦截器。该类还对外提供了三个方法: - `use`:添加一个拦截器到拦截器数组中,并返回一个`id`用于删除; - `forEach`:遍历拦截器数组; - `eject`:删除一个拦截器; 另外,我们给该类的泛型接口传入`T`,这是因为请求拦截器和响应拦截器添加的拦截函数参数类型不同,请求拦截器是`AxiosRequestConfig`类型,而响应拦截器是`AxiosResponse`类型。 # 5. 修改 Axios 类 定义好拦截器管理类之后,我们需要在`Axios`类上添加`interceptors`属性,并且该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例。并且,我们还需要修改`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中。 ## 5.1 添加 interceptors 属性 我们先在`Axios`类上添加`interceptors`属性: ```typescript // src/core/Axios.ts import { InterceptorManager } from "./InterceptorManager"; export class Axios { public interceptors: { request: InterceptorManager; response: InterceptorManager; }; constructor() { this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } } ``` ## 5.2 修改 request 方法 然后,我们修改`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中: ```typescript // src/core/Axios.ts import { InterceptorManager } from "./InterceptorManager"; export class Axios { public interceptors: { request: InterceptorManager; response: InterceptorManager; }; constructor() { this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } request(url: any, config?: any): AxiosPromise { if (typeof url === "string") { if (!config) { config = {}; } config.url = url; } else { config = url; } const chain: Array< | { resolved: ResolvedFn | ((config: AxiosRequestConfig) => AxiosPromise); rejected?: RejectedFn; } | undefined > = [ { resolved: dispatchRequest, rejected: undefined, }, ]; this.interceptors.request.forEach((interceptor) => { chain.unshift(interceptor); }); this.interceptors.response.forEach((interceptor) => { chain.push(interceptor); }); let promise = Promise.resolve(config); while (chain.length) { const { resolved, rejected } = chain.shift()!; promise = promise.then(resolved, rejected); } return promise; } } ``` 代码说明: - 首先,我们创建了一个数组`chain`,并把`dispatchRequest`函数赋值给`resolved`属性; - 然后,先遍历请求拦截器插入到`chain`的前面; - 接着,再遍历响应拦截器插入到`chain`的后面; - 接下来,我们定义一个已经 resolve 的`promise`,循环这个`chain`,拿到每个拦截器对象,把它们的`resolved`函数和`rejected`函数添加到`promise.then`的参数中,这样就相当于通过`Promise`的链式调用方式,实现了拦截器一层层的链式调用的效果; - 最后,返回`promise`; 需要注意的是:**请求拦截器是先添加的后执行,响应拦截器是先添加的先执行**。 # 6. 编写 demo OK,拦截器功能我们已经实现完毕了,接下来,我们就编写`demo`来测试下效果如何。 我们在 `examples` 目录下创建 `interceptors`目录,在 `interceptors`目录下创建 `index.`: ``` < lang="en"> interceptors demo ``` 接着再创建 `app.ts` 作为入口文件: ```typescript // interceptors/app.ts import axios from "../../src/index"; // 添加请求拦截器1 axios.interceptors.request.use( (config) => { config.headers.test += "requestInterceptor1---"; return config; }, (error) => { return Promise.reject(error); } ); // 添加请求拦截器2 axios.interceptors.request.use( (config) => { config.headers.test += "requestInterceptor2---"; return config; }, (error) => { return Promise.reject(error); } ); // 添加响应拦截器1 axios.interceptors.response.use( (res) => { res.data += "responseInterceptor1---"; return res; }, (error) => { return Promise.reject(error); } ); // 添加响应拦截器2 axios.interceptors.response.use( (res) => { res.data += "responseInterceptor2---"; return res; }, (error) => { return Promise.reject(error); } ); axios({ url: "/interceptor/get", method: "get", headers: { test: "", }, }).then((res) => { console.log(res); }); ``` 接着在 `server.js` 添加新的接口路由: ```javascript // 注册interceptor接口 router.get("/interceptor/get", function(req, res) { res.end("hello interceptor"); }); ``` 最后在根目录下的`index.`中加上启动该`demo`的入口: ```
  • interceptors
  • ``` OK,我们在命令行中执行: ```bash # 同时开启客户端和服务端 npm run server | npm start ``` 接着浏览器打开 `http://localhost:8000/` 即可访问我们的 `demo` 了,我们点击 `interceptors`,通过`F12`的 `network` 部分我们可以看到请求已正常发出,并且我们可以观察请求的`headers`部分和响应返回的`data`部分,如下: 请求`headers`部分: ![](~@/axios/13/01.png) 响应`data`部分: ![](~@/axios/13/02.png) 从图中我们可以看到:请求头的`test`字段先是经过请求拦截器 1 的处理,添加了`requestInterceptor1---`,然后又经过请求拦截器 2 的处理,在之前的基础上又添加了`requestInterceptor2---`;而响应返回的`data`字段先是经过响应拦截器 1 的处理,添加了`responseInterceptor1---`,然后又经过响应拦截器 2 的处理,在之前的基础上又添加了`responseInterceptor2---`。 这完全符合我们之前所想的:请求拦截器是先添加的后执行,响应拦截器是先添加的先执行。 # 7. 遗留问题 我们虽然已经实现了拦截器功能,但是目前还存在一个问题:如果我们添加了多个请求拦截器,其中某个拦截器修改了`data`字段,而后续的拦截器并没有获取到修改后的`data`字段,例如: ```typescript // 添加请求拦截器1 axios.interceptors.request.use( (config) => { config.data = { a: 1, }; return config; }, (error) => { return Promise.reject(error); } ); // 添加请求拦截器2 axios.interceptors.request.use( (config) => { console.log(config.data); return config; }, (error) => { return Promise.reject(error); } ); ``` 在请求拦截器 2 中打印`config.data`,我们期望的是打印出`{a:1}`,但是实际上打印出的是`undefined`,这是为什么呢? 这是因为:当我们第一个拦截器修改了`config`中的`data`后,`config`已经是一个普通对象了,而不再是`AxiosRequestConfig`类型了,所以我们在第二个拦截器中打印`config.data`时,`config`中已经没有`data`这个属性了。 这其实是一个`bug`,但是目前我们并不打算修复它,因为`axios`官方也存在这个`bug`,如下: ![](~@/axios/13/03.png) 关于这个`bug`的修复,我们会在后续的文章中专门讲解。 # 8. 总结 本篇文章我们实现了`axios`的拦截器功能,拦截器的本质是一个实现特定条件的`Promise`链式调用。我们定义了一个拦截器管理类`InterceptorManager`,用于管理拦截器,然后在`Axios`类上添加了`interceptors`属性,该属性又有两个属性`request`和`response`,它们都是`InterceptorManager`的实例。最后,我们修改了`request`方法,在发送请求前先获取所有拦截器,然后按照顺序插入到`Promise`链式调用中。 另外,我们还遗留了一个问题:多个请求拦截器中,如果某个拦截器修改了`data`字段,后续拦截器并没有获取到修改后的值,关于这个问题,我们会在后续文章中专门讲解。 最后,附上本节课源码地址:[https://github.com/wangtao0101/axios/tree/2020-05-13](https://github.com/wangtao0101/axios/tree/2020-05-13)

    栏目列表