在前端开发中,终止一个已经开始的网路请求是有一定应用场景的。尤其是在 SPA (Signal Page Web Application) 单页面 Web 应用中,路由的跳转或场景的切换并不会像多页面跳转一样终止之前未被响应的网络请求。此时就可能会发生数据请求发生时的场景已经不存在,而网络请求成功后,数据被不正确的渲染在新的场景中的问题。因此应确保这些网络请求在场景变化后被终止或不被处理,以避免数据的混乱,提升部分性能。
本文介绍如何终止一个 XHR、Fetch 或 Axios 请求。(Axios 是一个基于 XHR 和 Promise 封装的 HTTP 库,可用于 Web 和 Node.js,如果你不使用它,则不必关心 Axios 的部分。)
本文的完整 Demo 代码见于 GitHub: hooozen/web-demo。
终止一个 XHR 请求
XMLHttpRequest (本文中 XHR 为其简写)被浏览器广泛支持,功能完整,稳定性强,在 Ajax 中大量使用。
XHR 终止一个请求的操作是非常简单的,只需要调用该 XHR 实例对象的
abort
方法即可阻断当前未被响应的请求。
1 |
|
XMLHttpRequest 代理有 4 种状态,XHR 的当前状态由
readyState
属性记录。XHR 总处于下列状态之一:
值 | 状态 | 描述 |
---|---|---|
0 | UNSET | 代理被创建,但尚未调用 open() 方法。 |
1 | OPENED | open() 方法已经被调用 |
2 | HEADERS_RECEIVED | send() 方法已经被调用,并且头部和状态已经可获得。 |
3 | LOADING | 下载中,responseText 属性已经包含部分数据。 |
4 | DONE | 下载操作已完成。 |
当调用 abort()
方法时,readyState
会被置为
DONE,然后该请求被终止,状态变为 UNSET 状态。因此,在调用
abort()
方法后,再次调用 XHR
发送请求,仍然可以正常响应(除非再次调用了 abort()
方法终止了请求),这一点和 Fetch API 终止请求是不一样的。
终止一个 Fetch 请求
Fetch 请求返回一个 Promise 对象,而 Promise
有一个缺点就是一旦被“许诺”,要么被 resolve
要么被
reject
,不可被终止。所以一开始 Fetch
是不支持被终止的,所以开发者们不得不使用其他的方法来达到“终止”的效果。后来新的标准增加了
AbortController
接口,来终止 DOM
请求,但该接口目前仍处于“实验中”,不建议在生产中使用。
接下来分别介绍这两种方法:
使用 AbortController 终止 Fetch 请求
AbortController 是一个实验中的标准接口,提供 abort
方法来终止指定的 DOM 请求。
1 |
|
AbortController 实例化对象提供一个 signal
标记,当创建
Fetch 请求时,把该标记作为参数传入 Fetch 方法,则可以调用该实例化对象的
abort()
方法来终止被“标记”的 Fetch 请求。
与 XHR 的 abort()
方法不同的是,AbortController.abort()
方法一旦被调用,则被标记的 Fetch 不可再次发起请求,这一点和 Axios
中终止请求的机制是一致的。
使用 Promise.race() 封装一个可“终止”的 Fetch
因为 Promise 不能被终止,而 AbortController
接口稳定性和兼容性又很差,所以更多情况下需要采取其他方法来模拟“终止”Fetch请求。使用
Promise.race()
方法在需要时跳过对 Fetch
请求结果的处理来达到“终止”请求的效果。
Promise.race(iterable)
方法返回一个 promise,一旦迭代器中的某个 promise 解决或拒绝,返回的 promise 就会解决或拒绝。)
利用 Promise.race
的“竞速”特性,封装一个可以被“终止”的
_Fetch 接口:
1 |
|
上面代码封装了一个 _fetch
方法来进行网络请求,在_fetch().then()
中对网络请求进行处理。_fetch
方法返回了一个
Promise.race()
Promise,并在该 race()
中使用了一个 abortPromise
Promse 来与 fetch
Promise“竞速”。并将 abortPromise
的 reject
方法暴露出来。所以当调用被暴露出的 reject
方法时,如果此时
fetch 请求还没有完成(即没有被 resolve,或 reject),那么由于
Promise.race()
的“竞速”特性,_fetch
将返回
abortPromise
的 reject
,fetch Promise
将被忽略。
需要明确的是,上述方法并没有真正意义上终止一个网络请求,实际上网络请求没有受到任何影响,只不过通过封装的方法“跳过”了对希望被终止的请求的处理。这一点是和
XHR 和 AbortController 的 abort()
方法不同的。
终止一个 Axios 请求
Axios 提供了终止网络请求的方法,具体介绍可见官方文档中的 cancellation 部分,下面提供一个例子并对文档中未介绍的内容进行一些补充。
1 |
|
Axios 的 cancel()
方法实现的效果类似于 AbortController
接口的 abort()
方法,即一旦该方法被调用,则该实例就不再被允许再次发送网络请求。但 Axios
网络请求是基于 XHR 实现的,终止网络请求的方法也是调用了 XHR 的
abort()
方法。之所以其效果和 XHR.abort()
终止后仍可再次请求的效果不一致,是因为 Axios 基于 Promise
进行了封装,一旦调用了 cancel()
方法,Promise
就会抛出异常,并且被记录。下次再次调用该实例进行请求时,会直接抛出异常,不会发起网络请求。具体实现方法请查阅
Axios 源码。