欢迎大家来到 前端小课堂 的第五期,今天我们来聊一聊如何终止正在进行中的 Fetch 以及 Promise。文中会跟大家详细介绍这里面的两个关键知识点 AbortController 和 AbortSignal。对动手实践比较感兴趣的同学还可以看对应的视频版本。
大家在平时的开发过程中估计不会经常碰到需要主动取消一个 Fetch 请求的需求,所以一部分同学可能对这一块知识不是很了解。没有关系,看完这篇文章你就能够掌握关于如何终止一个 Fetch 请求或者一个 Promise 的全部技能了。那我们赶快开始吧~
这篇文章比我预期要花费的时间和精力还要多,所以文章比较长,大家现在没时间浏览的可以先收藏起来,以后慢慢看。如果觉得这篇文章不错的话,也可以帮忙点个赞,转发支持一下。
使用 AbortController 终止 Fetch 请求
在 fetch
之前,我们请求后端的资源使用的方式是通过 XMLHttpRequest
这个构造函数,创建一个 xhr
对象,然后通过这个 xhr
对象进行请求的发送以及接收。
const xhr = new XMLHttpRequest();
xhr.addEventListener('load', function (e) {
console.log(this.responseText);
});
xhr.open('GET', 'https://jsonplaceholder.typicode.com/todos/1');
xhr.send();
这个 xhr
上也存在一个 abort
方法用来进行请求的终止操作。但是需要注意的是,这个 abort
的执行过程是比较模糊的。 我们不清楚 abort
在什么时候可以不进行或终止对应的网络请求,又或者如果在调用 abort
方法和获取到请求的资源之间存在竞争条件的时候会发生什么。我们可以通过简单的代码来实践一下:
// ... 省略掉上面的代码
setTimeout(() => {
xhr.abort();
}, 10);
通过添加一个延时,然后取消掉对应的请求;在控制台可以看到,有时请求已经获取到结果了,但是却没有打印出对应的结果;有时请求没有获取到对应的结果,但是查看对应的网络的状态却是成功的。所以这里面有很多的不确定性,跟我们的感觉是比较模糊的。
等到 fetch
出来的时候,大家就在讨论关于如何正确,清楚地取消一个 fetch
请求。最早的讨论可以看这里 Aborting a fetch #27 ,那已经是7年前(2015年)的事情了,可以看到当时的讨论还是比较激烈的。大家感兴趣的话可以看看当时大家都主要关注的是哪些特性。
最终,新的规范 出来了,通过 AbortController
和 AbortSignal
我们可以方便,快捷,清楚地终止一个 fetch
请求。要注意的是,这个规范是一个 DOM 层面的规范,不是 JavaScript 语言层面的规范。现在绝大多数的浏览器环境和新版本的 Node.js 环境也都支持这个特性了。关于 AbortController
的兼容性,大家可以参考这里 AbortController#browser_compatibility
下面文章中的代码例子基本上都可以直接复制粘贴到控制台运行的,所以感兴趣的同学阅读到对应的部分可以直接打开浏览器的控制台去运行一下,然后看看对应的结果。加深一下自己对相关知识点的记忆。
终止正在进行中的单个请求
我们先通过一段代码来给大家展示一下如何实现这个功能
const ac = new AbortController();
const {
signal } = ac;
const resourceUrl = 'https://jsonplaceholder.typicode.com/todos/1';
fetch(resourceUrl, {
signal })
.then(response => response.json())
.then(json => console.log(json))
.catch(err => {
// 不同浏览器的返回结果不同
console.log(err);
});
// 可以立即终止请求,或者设置一个定时器
// ac.abort();
setTimeout(() => {
ac.abort();
}, 10);
大家感兴趣的话也可以把上面的代码复制粘贴到浏览器的控制台运行一下,上面代码的运行结果如下所示:
可以看到控制台的 Console 的输出是:DOMException: The user aborted a request.
对应的 Network 展示的是一个取消状态的请求。这说明我们刚才发送的请求被终止取消掉了。
能够在一些特定的情况下主动地取消相关的请求对我们应用来说是很重要的,这能够减少我们用户的流量使用以及我们应用的内存使用。
AbortController 的深入剖析
接下来我们来讲解一下上面的代码,第一行通过 AbortController
创建了一个 AbortController
类型的实例 ac
,这个实例上有一个 abort
方法和一个 AbortSignal
类型的 signal
实例。然后我们通过 fetch
方法去请求一个资源路径,传递给 fetch
的选项把 ac
的 signal
对象传递进去。fetch
方法如果获取到了资源就会把资源打印到控制台,如果网络发生了问题,就会捕获异常,然后把异常打印到控制台。最后,通过一个 setTimeout
延时,调用 ac
的 abort
方法终止 fetch
请求 。
fetch
的 options
选项允许我们传递一个 signal
对象;fetch
的内部会监测这个对象的状态,如果这个对象的状态从未终止的状态变为终止的状态的话,并且 fetch
请求还在进行中的话,fetch
请求就会立即失败。其对应的 Promise
的状态就会变为 Rejected
。
如何改变 signal
的状态呢?我们可以通过调用 ac
的 abort
方法去改变 signal
的状态。一旦我们调用了 ac.abort()
那么与之关联的 signal
的状态会立刻从起始状态(非终止状态)转变为终止状态。
我们上面只是简单地使用了 signal
对象,这个对象是 AbortSignal
类的实例,对于 AbortSignal
我们下面会做深入的讲解,这里暂时只需要知道 signal
可以作为一个信号对象传递给 fetch
方法,可以用来终止 fetch
的继续进行。
另外,在不同的浏览器中打印的结果可能略有不同,这个跟不同浏览器的内部实现有关系。比如在 Firefox 中的结果如下:
在 Safari 中的结果如下:
当然如果我们没有终止 fetch
请求的话,控制台的打印将会是:
另外大家如果需要一些模拟的数据接口的话可以试试 JSONPlaceholder ,还是很方便使用的。
批量取消多个 fetch 请求
值得注意的