当前位置 : 首页 > 保养数据

Axios拦截器实现与异常处理全解析

<|begin▁of▁sentence|># 1. 前言 在之前的文章中,我们介绍了`axios`的拦截器,并用流程图说明了拦截器在`axios`整体请求与响应流程中起的作用,最后我们实现了拦截器管理类以及链式调用逻辑。今天我们就把拦截器运用到`axios`中去。 # 2. 修改 Axios 类 我们之前实现的`Axios`类中只有一个简单`request`方法,如下: ```typescript request(url: any, config?: any): AxiosPromise { if (typeof url === "string") { if (!config) { config = {}; } config.url = url; } else { config = url; } return dispatchRequest(config); } ``` 接下来,我们要把之前写好的拦截器运用其中,首先,在`Axios`类中定义两个属性,分别为拦截器管理类的实例,如下: ```typescript import { InterceptorManager } from "./InterceptorManager"; export class Axios { defaults: AxiosRequestConfig; interceptors: { request: InterceptorManager; response: InterceptorManager; }; constructor(initConfig: AxiosRequestConfig) { this.defaults = initConfig; this.interceptors = { request: new InterceptorManager(), response: new InterceptorManager(), }; } // ... } ``` 然后,我们还要修改`request`方法,把拦截器的链式调用逻辑添加到该方法中,如下: ```typescript request(url: any, config?: any): AxiosPromise { if (typeof url === "string") { if (!config) { config = {}; } config.url = url; } else { config = url; } // 组成链条 const chain: PromiseChain[] = [ { resolved: dispatchRequest, rejected: undefined, }, ]; // 请求拦截器添加到链条前面 this.interceptors.request.forEach((interceptor) => { chain.unshift(interceptor); }); // 响应拦截器添加到链条后面 this.interceptors.response.forEach((interceptor) => { chain.push(interceptor); }); // 把config也包装成一个Promise let promise = Promise.resolve(config); // 遍历链条,依次执行 while (chain.length) { const { resolved, rejected } = chain.shift()!; promise = promise.then(resolved, rejected); } return promise; } ``` OK,这样我们就把拦截器的链式调用逻辑添加到`request`方法中了。 # 3. 修改 dispatchRequest 函数 我们之前实现的`dispatchRequest`函数如下: ```typescript function dispatchRequest(config: AxiosRequestConfig): AxiosPromise { processConfig(config); return xhr(config).then( (res) => { return transformResponseData(res); }, (e) => { return Promise.reject(e); } ); } ``` 该函数内部调用了`xhr`函数,并且将`xhr`函数返回的`Promise`对象直接返回了,但是我们在之前介绍拦截器的时候说过,拦截器的执行顺序是: 请求拦截器 -> 分发请求 -> 响应拦截器 所以,`dispatchRequest`函数返回的应该是`xhr`函数返回的`Promise`对象,然后经过响应拦截器处理后再返回给用户。 但是,我们观察上面的`request`方法,在组成调用链的时候,我们只把`dispatchRequest`函数放到了调用链中,而`dispatchRequest`函数内部调用的`xhr`函数是异步的,它返回的是`Promise`对象,而调用链中处理的是`config`对象,并不是`Promise`对象,所以这样写是有问题的。 我们需要修改`dispatchRequest`函数,让它返回一个`Promise`对象,并且在`Promise`对象内部执行`xhr`函数,如下: ```typescript function dispatchRequest(config: AxiosRequestConfig): AxiosPromise { return new Promise((resolve, reject) => { processConfig(config); xhr(config).then( (res) => { resolve(transformResponseData(res)); }, (e) => { reject(e); } ); }); } ``` 这样,`dispatchRequest`函数返回的就是一个`Promise`对象,并且该`Promise`对象内部执行了`xhr`函数,这样就可以保证在调用链中,`dispatchRequest`函数返回的是`Promise`对象,然后调用链会等待该`Promise`对象`resolve`后,再把`resolve`的值传递给下一个拦截器。 # 4. 编写 demo 接下来,我们编写 `demo` 来测试下拦截器是否有效。 ```javascript const axios = new Axios({ baseURL: "http://localhost:3000", }); // 添加请求拦截器 axios.interceptors.request.use( (config) => { console.log("request interceptor1 resolved"); return config; }, (error) => { console.log("request interceptor1 rejected"); return Promise.reject(error); } ); axios.interceptors.request.use( (config) => { console.log("request interceptor2 resolved"); return config; }, (error) => { console.log("request interceptor2 rejected"); return Promise.reject(error); } ); // 添加响应拦截器 axios.interceptors.response.use( (res) => { console.log("response interceptor1 resolved"); return res; }, (error) => { console.log("response interceptor1 rejected"); return Promise.reject(error); } ); axios.interceptors.response.use( (res) => { console.log("response interceptor2 resolved"); return res; }, (error) => { console.log("response interceptor2 rejected"); return Promise.reject(error); } ); axios.request({ url: "/api/base/get", params: { a: 1, b: 2, }, }); ``` 我们为`axios`的请求和响应分别添加了两个拦截器,并且在每个拦截器中都打印了相应的日志,另外,我们发送了一个简单的请求。 我们运行该`demo`,发现控制台打印顺序如下: ```javascript request interceptor2 resolved request interceptor1 resolved response interceptor1 resolved response interceptor2 resolved ``` 从打印顺序可以看出: - 请求拦截器是先添加的后执行(栈:先进后出); - 响应拦截器是先添加的先执行(队列:先进先出); 另外,我们观察`network`中发送的请求,发现请求已经正常发出,并且返回了响应。 ![](~@/axios/13/01.png) OK,这样我们就为`axios`实现了拦截器功能。 # 5. 遗留问题 虽然我们已经为`axios`实现了拦截器功能,但是目前还是存在一些问题的。 ## 5.1 请求拦截器异常情况 如果请求拦截器中抛出异常,或者返回`Promise.reject(error)`,那么会怎样? 我们来修改`demo`,在第二个请求拦截器中返回`Promise.reject(error)`,如下: ```javascript // 添加请求拦截器 axios.interceptors.request.use( (config) => { console.log("request interceptor1 resolved"); return config; }, (error) => { console.log("request interceptor1 rejected"); return Promise.reject(error); } ); axios.interceptors.request.use( (config) => { console.log("request interceptor2 resolved"); return Promise.reject("error in request interceptor2"); }, (error) => { console.log("request interceptor2 rejected"); return Promise.reject(error); } ); ``` 然后运行`demo`,发现控制台打印顺序如下: ```javascript request interceptor2 resolved request interceptor1 rejected response interceptor1 rejected response interceptor2 rejected ``` 从打印顺序可以看出: - 第二个请求拦截器`resolved`函数中返回了`Promise.reject("error in request interceptor2")`,所以该拦截器的`resolved`函数返回的是一个`rejected`状态的`Promise`对象; - 然后调用链会执行该拦截器的`rejected`函数,但是该拦截器我们并没有设置`rejected`函数,所以会跳过,然后调用链会继续执行下一个拦截器,即第一个请求拦截器,但是第一个请求拦截器的`resolved`函数并没有执行,而是执行了它的`rejected`函数,并且把第二个请求拦截器`resolved`函数返回的`rejected`状态的`Promise`对象的`reason`作为参数传递给了第一个请求拦截器的`rejected`函数; - 接着调用链继续执行,由于第一个请求拦截器的`rejected`函数返回的也是`rejected`状态的`Promise`对象,所以会跳过所有响应拦截器的`resolved`函数,而是执行它们的`rejected`函数; 所以,最终请求并没有发送出去,而是走到了响应拦截器的`rejected`函数中。 ## 5.2 响应拦截器异常情况 如果响应拦截器中抛出异常,或者返回`Promise.reject(error)`,那么会怎样? 我们来修改`demo`,在第二个响应拦截器中返回`Promise.reject(error)`,如下: ```javascript // 添加响应拦截器 axios.interceptors.response.use( (res) => { console.log("response interceptor1 resolved"); return res; }, (error) => { console.log("response interceptor1 rejected"); return Promise.reject(error); } ); axios.interceptors.response.use( (res) => { console.log("response interceptor2 resolved"); return Promise.reject("error in response interceptor2"); }, (error) => { console.log("response interceptor2 rejected"); return Promise.reject(error); } ); ``` 然后运行`demo`,发现控制台打印顺序如下: ```javascript request interceptor2 resolved request interceptor1 resolved response interceptor1 resolved response interceptor2 resolved ``` 从打印顺序可以看出: - 请求拦截器都正常执行了; - 然后发送了请求,并且收到了响应; - 接着执行响应拦截器,第一个响应拦截器的`resolved`函数正常执行,然后返回了`res`; - 接着执行第二个响应拦截器的`resolved`函数,但是该函数返回了`Promise.reject("error in response interceptor2")`,所以该拦截器的`resolved`函数返回的是一个`rejected`状态的`Promise`对象; - 然后调用链会执行该拦截器的`rejected`函数,但是该拦截器我们并没有设置`rejected`函数,所以会跳过,然后调用链会继续执行下一个拦截器,但是后面已经没有拦截器了,所以最终返回的是第二个响应拦截器`resolved`函数返回的`rejected`状态的`Promise`对象; 所以,最终请求发送出去了,并且收到了响应,但是响应拦截器返回的是`rejected`状态的`Promise`对象,所以用户接收到的响应是`rejected`状态的。 ## 5.3 总结 从以上两种异常情况可以看出,我们的拦截器在处理异常情况时,行为是符合预期的。但是,我们目前并没有对异常情况进行处理,比如在拦截器中抛出异常,或者返回`Promise.reject(error)`,我们只是简单的打印了日志,并没有做其他处理。在实际项目中,我们可能需要对异常情况进行统一处理,比如弹出错误提示等。 # 6. 示例代码 至此,我们已经为`axios`实现了拦截器功能,完整代码见:[https://github.com/wangta69/learn-axios](https://github.com/wangta69/learn-axios) # 7. 总结 在本篇文章中,我们首先修改了`Axios`类,为其添加了拦截器管理类的实例属性,然后修改了`request`方法,把拦截器的链式调用逻辑添加到该方法中,接着修改了`dispatchRequest`函数,让其返回一个`Promise`对象,并且在`Promise`对象内部执行`xhr`函数,最后我们编写了`demo`测试拦截器是否有效,并且分析了拦截器在处理异常情况时的行为。 从下一篇文章开始,我们将开始对`axios`的配置化进行重构。

栏目列表