使用AbortController取消延迟Fetch请求:两种实现差异原因解析
为什么两种AbortController实现会有差异?
场景背景
我们需要实现:点击按钮发起fetch请求时,若重复点击,则取消之前正在进行的请求,仅保留最新请求。为模拟网络延迟,将fetch包装在带setTimeout的Promise中。初始实现未达到预期,修改后逻辑正常,核心差异在于AbortController的管理时机和作用域。
初始失败实现的问题
function fetchProducts() { return fetchDataWithDelay(API_BASE_URL + '/products.json'); } function fetchDataWithDelay(url, delay = 3000) { if (!url) throw new Error('URL is required.'); if (controller) { controller.abort('Fetch aborted by the user.'); console.log('Fetch aborted.'); } controller = new AbortController(); // 覆盖全局controller return new Promise((resolve, reject) => { console.log(`Fetching data with ${delay}ms delay...`); setTimeout(async () => { // 这里引用的是全局controller的最终值 const response = await fetch(url, { signal: controller.signal }); // ...后续处理 }, delay); }); }
问题根源
当快速重复点击按钮时:
- 第一次调用
fetchDataWithDelay:创建controller A并赋值给全局变量,然后启动setTimeout(3秒后执行fetch)。 - 第二次调用
fetchDataWithDelay:先abort全局的controller A,然后创建controller B覆盖全局变量,启动新的setTimeout。 - 3秒后,第一次请求的
setTimeout执行fetch,但此时全局controller已经是B了——fetch用的是未被abort的controller B的signal,所以请求正常完成,不会被取消。
简单说:旧请求的fetch最终引用的是后续请求创建的新controller,旧controller的abort操作根本没作用到旧请求的fetch上。
修改后成功实现的逻辑
function fetchProducts() { if (controller) { controller.abort('Fetch aborted by the user.'); console.log('Fetch aborted.'); } controller = new AbortController(); // 创建当前请求的controller return fetchDataWithDelay(API_BASE_URL + '/products.json', controller); // 传递给延迟函数 } function fetchDataWithDelay(url, abortController, delay = 3000) { if (!url) throw new Error('URL is required.'); return new Promise((resolve, reject) => { console.log(`Fetching data with ${delay}ms delay...`); setTimeout(async () => { // 引用的是当前请求专属的controller的signal const response = await fetch(url, { signal: abortController.signal }); // ...后续处理 }, delay); }); }
正确逻辑分析
当重复点击时:
- 第一次调用
fetchProducts:创建controller A,传递给fetchDataWithDelay,启动setTimeout(fetch将使用controller A的signal)。 - 第二次调用
fetchProducts:先abort全局的controller A,此时controller A的signal触发abort事件;然后创建controller B,传递给新的fetchDataWithDelay。 - 3秒后,第一次请求的
setTimeout执行fetch,此时用的是已经被abort的controller A的signal,fetch直接抛出取消错误,请求被终止。 - 第二次请求的fetch使用专属的controller B的signal,直到下一次点击被abort。
核心差异总结
- 初始实现中,延迟执行的fetch依赖全局变量的动态值,旧请求的signal被后续请求的controller覆盖,导致abort操作无效。
- 修改后实现中,每个请求的fetch绑定专属的controller实例,后续请求abort的是旧请求对应的controller,确保旧请求能接收到取消信号。
内容的提问来源于stack exchange,提问作者ddZ6ii
相关产品推荐
相关产品推荐

