我们知道,fetch
返回一个 Promise。而 JavaScript 通常没有“中止” Promise 的概念。那么我们如何取消正在进行的 fetch
呢?例如,如果用户在我们网站上的操作表明不再需要 fetch
了。
有一个专门用于此目的的内置对象:AbortController
。它不仅可以用于中止 fetch
,还可以用于中止其他异步任务。
使用方法非常简单
AbortController 对象
创建控制器
let controller = new AbortController();
控制器是一个非常简单的对象。
- 它只有一个方法
abort()
, - 以及一个属性
signal
,允许在其上设置事件监听器。
当调用 abort()
时
controller.signal
会发出"abort"
事件。controller.signal.aborted
属性变为true
。
通常,我们在过程中有两个参与者
- 执行可取消操作的一方,它在
controller.signal
上设置一个监听器。 - 取消操作的一方:它在需要时调用
controller.abort()
。
以下是一个完整的示例(还没有使用 fetch
)
let controller = new AbortController();
let signal = controller.signal;
// The party that performs a cancelable operation
// gets the "signal" object
// and sets the listener to trigger when controller.abort() is called
signal.addEventListener('abort', () => alert("abort!"));
// The other party, that cancels (at any point later):
controller.abort(); // abort!
// The event triggers and signal.aborted becomes true
alert(signal.aborted); // true
正如我们所见,AbortController
只是在调用 abort()
时传递 abort
事件的一种方式。
我们可以自己实现相同类型的事件监听,而无需 AbortController
对象。
但有价值的是,fetch
知道如何使用 AbortController
对象。它已集成到其中。
与 fetch 一起使用
为了能够取消 fetch
,将 AbortController
的 signal
属性作为 fetch
选项传递
let controller = new AbortController();
fetch(url, {
signal: controller.signal
});
fetch
方法知道如何使用 AbortController
。它将监听 signal
上的 abort
事件。
现在,要中止,请调用 controller.abort()
controller.abort();
我们完成了:fetch
从 signal
获取事件并中止请求。
当 fetch 被中止时,它的 promise 会拒绝并抛出一个 AbortError
错误,因此我们应该处理它,例如在 try..catch
中。
以下是一个完整的示例,其中 fetch
在 1 秒后被中止
// abort in 1 second
let controller = new AbortController();
setTimeout(() => controller.abort(), 1000);
try {
let response = await fetch('/article/fetch-abort/demo/hang', {
signal: controller.signal
});
} catch(err) {
if (err.name == 'AbortError') { // handle abort()
alert("Aborted!");
} else {
throw err;
}
}
AbortController 可扩展
AbortController
可扩展。它允许同时取消多个 fetch。
以下是一个代码草图,它并行获取多个 urls
,并使用单个控制器中止所有这些请求
let urls = [...]; // a list of urls to fetch in parallel
let controller = new AbortController();
// an array of fetch promises
let fetchJobs = urls.map(url => fetch(url, {
signal: controller.signal
}));
let results = await Promise.all(fetchJobs);
// if controller.abort() is called from anywhere,
// it aborts all fetches
如果我们有自己的异步任务,与 fetch
不同,我们可以使用单个 AbortController
来停止这些任务,以及 fetch。
我们只需要在我们的任务中监听它的 abort
事件
let urls = [...];
let controller = new AbortController();
let ourJob = new Promise((resolve, reject) => { // our task
...
controller.signal.addEventListener('abort', reject);
});
let fetchJobs = urls.map(url => fetch(url, { // fetches
signal: controller.signal
}));
// Wait for fetches and our task in parallel
let results = await Promise.all([...fetchJobs, ourJob]);
// if controller.abort() is called from anywhere,
// it aborts all fetches and ourJob
总结
AbortController
是一个简单的对象,当调用abort()
方法时,它会在其signal
属性上生成一个abort
事件(并将signal.aborted
设置为true
)。fetch
与它集成:我们将signal
属性作为选项传递,然后fetch
监听它,因此可以中止fetch
。- 我们可以在代码中使用
AbortController
。 "调用abort()
" → “监听abort
事件” 的交互简单且通用。即使没有fetch
,我们也可以使用它。
评论
<code>
标签,对于多行代码,请将它们包装在<pre>
标签中,对于超过 10 行的代码,请使用沙箱 (plnkr,jsbin,codepen…)