1.使用Typescript重构axios(一)——写在最前面
2.使用Typescript重构axios(二)——项目起手,跑通流程
3.使用Typescript重构axios(三)——实现基础功能:处理get请求url参数
4.使用Typescript重构axios(四)——实现基础功能:处理post请求参数
5.使用Typescript重构axios(五)——实现基础功能:处理请求的header
6.使用Typescript重构axios(六)——实现基础功能:获取响应数据
7.使用Typescript重构axios(七)——实现基础功能:处理响应header
8.使用Typescript重构axios(八)——实现基础功能:处理响应data
9.使用Typescript重构axios(九)——异常处理:基础版
10.使用Typescript重构axios(十)——异常处理:增强版
11.使用Typescript重构axios(十一)——接口扩展
12.使用Typescript重构axios(十二)——增加参数
13.使用Typescript重构axios(十三)——让响应数据支持泛型
14.使用Typescript重构axios(十四)——实现拦截器
15.使用Typescript重构axios(十五)——默认配置
16.使用Typescript重构axios(十六)——请求和响应数据配置化
17.使用Typescript重构axios(十七)——增加axios.create
18.使用Typescript重构axios(十八)——请求取消功能:总体思路
19.使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式
20.使用Typescript重构axios(二十)——请求取消功能:实现第一种使用方式
21.使用Typescript重构axios(二十一)——请求取消功能:添加axios.isCancel接口
22.使用Typescript重构axios(二十二)——请求取消功能:收尾
23.使用Typescript重构axios(二十三)——添加withCredentials属性
24.使用Typescript重构axios(二十四)——防御XSRF攻击
25.使用Typescript重构axios(二十五)——文件上传下载进度监控
26.使用Typescript重构axios(二十六)——添加HTTP授权auth属性
27.使用Typescript重构axios(二十七)——添加请求状态码合法性校验
28.使用Typescript重构axios(二十八)——自定义序列化请求参数
29.使用Typescript重构axios(二十九)——添加baseURL
30.使用Typescript重构axios(三十)——添加axios.getUri方法
31.使用Typescript重构axios(三十一)——添加axios.all和axios.spread方法
32.使用Typescript重构axios(三十二)——写在最后面(总结)
项目源码请猛戳这里!!!
1. 前言
通过上篇文章分析,我们知道:
请求配置对象中有一个请求取消令牌 cancelToken 属性,该属性对应一个取消请求的触发函数; 当在请求外部调用了该触发函数,表示此时需要取消请求了,那么我们此时调用 XMLHttpRequest 对象上的 abort() 方法将请求取消即可。 axios 混合对象上又多了一个静态接口 CancelToken ; CancelToken 接口是一个类; CancelToken 类的构造函数接收一个函数作为参数; 并且这个参数函数也接收一个取消函数作为参数;接下来,我们就基于以上这些信息来实现取消请求的第二种使用方式。
2. 定义接口类型
在创建 CancelToken 类之前,我们先在 src/types/index.ts 中定义一下相关的接口类型。
CancelToken 类的实例对象接口类型
CancelToken 类的实例对象包含两个参数:一个必选参数是 promise ,类型是 Promise<string> ,因为它要接收字符串类型的取消原因作为参数;另一个是可选参数取消原因 reason ,类型是 string 。
export interface CancelToken { promise:Promise<string> reason?:string }
CancelToken 类的构造函数的参数类型
因为 CancelToken 类的构造函数接收一个函数作为参数,因此,我们也需要定义一个该参数函数的类型。该参数函数又接收一个取消函数作为参数,它的类型是 Canceler
export interface CancelExecutor { (cancel: Canceler): void }
取消函数类型 Canceler
取消函数接收错误原因 message 作为参数。
export interface Canceler { (message?: string): void }
修改 AxiosRequestConfig
既然请求配置对象上多了一个 cancelToken 属性,那当然需要再请求配置对象的接口类型定义上添加该属性。
export interface AxiosRequestConfig { // ... cancelToken?:CancelToken // ... }
3. 创建CancelToken类
接口定义好之后,我们就可以来创建 CancelToken 类了,我们在 src 下新建 cancel 目录,并在该目录下创建 CancelToken.ts ,在该文件内创建 CancelToken 类,如下:
import { CancelExecutor } from "types"; interface ResolvePromise { (reason?: string): void; } export default class CancelToken { promise: Promise<string>; reason?: string; constructor(executor: CancelExecutor) { let resolvePromise: ResolvePromise; this.promise = new Promise<string>(resolve => { resolvePromise = resolve; }); executor(message => { if (this.reason) { return; } this.reason = message; resolvePromise(this.reason); }); } }
代码说明:
在 CancelToken 构造函数内部,首先实例化了一个 pending 状态的 Promise 对象,然后用一个 resolvePromise 变量指向 resolve 函数。
接着执行 executor 函数,该函数接收的参数是: cancel 函数,即:
message => { if (this.reason) { return; } this.reason = message; resolvePromise(this.reason); }
cancel 函数就是将来的请求取消触发函数,当外部调用了 cancel 函数,在 cancel 函数内部,会调用 resolvePromise 把 Promise 对象从 pending 状态变为 resolved 状态。
4. 实现请求取消逻辑
上篇文章说过,实现请求取消实际上是调用了 XMLHttpRequest 对象上的 abort() 方法,在本项目中,能接触到 XMLHttpRequest 对象的就只有在 src/core/xhr.ts 中的请求核心函数 xhr() 内了,所以我们需要在该函数中实现请求取消的逻辑,并且请求取消指的是发出请求后取消,所以我们需要把这段逻辑实现在 xhr() 函数内的 request.send() 之后,如下:
const { // ... cancelToken } = config; // ... // 3.发送请求 request.send(data); if (cancelToken){ cancelToken.promise.then(reason => { request.abort(); reject(reason) }) } // ...
代码说明:
首先判断用户是否配置的 cancelToken ,如果没有配置,表示没有取消请求这项需求; 如果配置了 cancelToken ,并且当外部调用了请求取消触发函数,此时 cancelToken.promise 会变成 resolved 状态,然后就会执行 then 函数,在 then 函数内部调用 XMLHttpRequest 对象上的 abort() 方法取消请求。OK,请求取消的逻辑就已经实现完了,接下来我们为 axios 混合对象添加 CancelToken 静态接口。
5. 添加CancelToken接口
给 axios 混合对象添加接口非常简单,我们已经添加过好几回了,我们仿照上次添加 create 接口那样,首先在 src/types/index.ts 中的 axios 混合对象接口定义中添加 CancelToken ,如下:
export interface CancelTokenStatic { new(executor: CancelExecutor): CancelToken } export interface AxiosStatic extends AxiosInstance { create(config?: AxiosRequestConfig): AxiosInstance; CancelToken:CancelTokenStatic }
由于 CancelToken 是一个类,我们还要为这个类定义一个类类型接口 CancelTokenStatic ,该接口里面有一个构造函数接口。
添加好接口类型以后,我们就可以在 src/axios.ts 中给 axios 混合对象添加 CancelToken 接口了,如下:
import CancelToken from "./cancel/CancelToken"; axios.CancelToken = CancelToken
OK,到此为止,官方 axios 取消请求的第二种使用方式就实现好了,在做 demo 之前,我们先来梳理一下整个流程。
6. 使用流程梳理
我们使用如下方式取消请求时,代码内部的流程都是怎样的,我们现在就来梳理下。
const CancelToken = axios.CancelToken; let cancel; axios.get('/user/12345', { cancelToken: new CancelToken(function executor(c) { // An executor function receives a cancel function as a parameter // executor函数接收一个取消函数作为参数 cancel = c; }) }); cancel('Operation canceled by the user.');
流程梳理:
用户为请求配置了 cancelToken 属性,该属性的属性值是 CancelToken 类的实例。
实例化 CancelToken 类时,会执行类的构造函数,我们为构造函数传入了一个 executor 函数,如下:
function executor(c) { // An executor function receives a cancel function as a parameter // executor函数接收一个取消函数作为参数 cancel = c; }
该函数接收一个参数,并把这个参数赋给了变量 cancel ;
在构造函数内部,首先实例化了一个 pending 状态的 Promise 对象,然后用一个 resolvePromise 变量指向 resolve 函数。
接着执行了传入的 executor 函数,在执行 executor 函数的时候为其传入了一个参数,该参数如下:
message => { if (this.reason) { return; } this.reason = message; resolvePromise(this.reason); }
执行了 executor 函数,该函数会把这个参数赋给变量 cancel ,即变量 cancel 为:
let cancel = message => { if (this.reason) { return; } this.reason = message; resolvePromise(this.reason); }
变量 cancel 就是将来的请求取消触发函数,当外部取消请求时就会调用 cancel 函数,在 cancel 函数内部,会调用 resolvePromise 把 Promise 对象从 pending 状态变为 resolved 状态,也就是说把 CancelToken 类中的 this.promise 变成了 resolved 状态。
而请求配置对象中的 cancelToken 属性是 CancelToken 类的实例对象,那么它自然能够访问到类里面的 promise 属性,当该属性的状态变成 resolved 时,表明有人在外面调用了 cancel 函数,此时它就通过
cancelToken.promise.then(reason => { request.abort(); reject(reason) })
来取消请求。
如果没有人调用 cancel 函数,那么 cancelToken.promise 的状态就一直是 pendding ,就不能调用 then 方法,就不能取消请求。
以上就是代码的整个运行流程。接下来我们就来编写 demo 来测试以上流程是否正确。
7. demo编写
在 examples 目录下创建 cancel 目录,在 cancel 目录下创建 index.html :
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8" /> <title>cancel demo</title> </head> <body> <script src="/__build__/cancel.js"></script> </body> </html>
接着再创建 app.ts 作为入口文件:
import axios from "src/axios"; import { Canceler } from "src/types"; const CancelToken = axios.CancelToken; let cancel: Canceler; axios.get("/api/cancel", { cancelToken: new CancelToken(c => { cancel = c; }) }) .catch(function(e) { console.log(e); }); setTimeout(() => { cancel("Operation canceled by the user"); }, 1000);
接着在 server/server.js 添加新的接口路由:
// 取消请求 router.get("/api/cancel", function(req, res) { setTimeout(() => { res.json({ msg: `hello world` }); }, 3000); });
我们设置响应在发出收到请求3秒后再响应,而在请求中,我们配置的是请求发出后1秒就取消请求,从而验证是否能够取消请求。
最后在根目录下的 index.html 中加上启动该 demo 的入口:
<li><a href="examples/cancel">cancel</a></li>
OK,我们在命令行中执行:
# 同时开启客户端和服务端 npm run server | npm start
接着我们打开 chrome 浏览器,访问 http://localhost:8000/ 即可访问我们的 demo 了,我们点击 cancel ,通过 F12 的 network 部分我们可以看到:请求发出一秒后请求状态变成 canceled ,表明请求已经被成功取消了。
然后我们将 demo 中的取消请求触发函数注释,
//setTimeout(() => { // cancel("Operation canceled by the user"); //}, 1000);
再发送请求,我们看到3秒后请求又可以正常得到响应了。
OK,取消请求的第二种使用方式就已经实现完毕了。
(完)
查看更多关于使用Typescript重构axios(十九)——请求取消功能:实现第二种使用方式的详细内容...