在 JavaScript 中处理异步操作时,通常需要灵活的控制机制来中止任务。AbortController API 提供了一个强大且通用的方式来终止异步操作,不仅可以中断 HTTP 请求,还可以应用于多种异步任务,如事件监听、流操作等。在本文中,我们将详细探讨 AbortController 的使用场景、AbortSignal 的静态方法、事件处理中的中止机制,以及在实际开发中的一些最佳实践。
AbortController 简介
AbortController 是 JavaScript 中的全局类,专门用于管理和中止异步操作。通过创建一个 AbortController 实例,我们可以获取两个核心功能:
- signal 属性:用于监控是否有中止信号发出,能够传递给异步操作,比如 fetch 请求或事件监听器。
 - .abort() 方法:可以随时手动触发中止信号,进而取消异步操作。
 
基本用法如下:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
const signal = controller.signal;
fetch('/api/data', { signal })
  .then(response => response.json())
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch request was aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });
// 取消请求
controller.abort();
        事件监听器的取消
在事件处理中,我们可以通过将 AbortController.signal 传递给 addEventListener 的第三个参数,使得在中止时自动移除事件监听器:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
const button = document.querySelector('#myButton');
button.addEventListener('click', () => {
  console.log('Button clicked');
}, { signal: controller.signal });
// 在某个条件下中止事件监听
controller.abort();
        这样,当调用 controller.abort() 时,按钮的点击事件监听器会被自动移除,避免冗余的事件绑定和内存泄漏。
Fetch 请求的中断
fetch 请求原生支持 AbortSignal,可以在请求发出后随时中断:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
fetch('/api/long-running-task', { signal: controller.signal })
  .then(response => response.json())
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch request aborted');
    } else {
      console.error('Fetch failed:', err);
    }
  });
// 稍后取消请求
setTimeout(() => {
  controller.abort();
}, 5000);  // 5秒后取消请求
        在网络请求延时过长或用户切换页面等场景下,中断 fetch 请求可以显著提升用户体验。
Axios 请求的中断
从 v0.22.0 开始,Axios 支持以 fetch API 方式------ AbortController 取消请求:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()
        - 当然还可以使用 
CancelToken取消一个请求,但需要注意的是此 API 从 v0.22.0 开始已被弃用,不应在新项目中使用。 
            
            
              js
              
              
            
          
          const CancelToken = axios.CancelToken;
const source = CancelToken.source();
axios.get('/user/12345', {
  cancelToken: source.token
}).catch(function (thrown) {
  if (axios.isCancel(thrown)) {
    console.log('Request canceled', thrown.message);
  } else {
    // 处理错误
  }
});
axios.post('/user/12345', {
  name: 'new name'
}, {
  cancelToken: source.token
})
// 取消请求(message 参数是可选的)
source.cancel('Operation canceled by the user.');
        Node.js 请求的中断
在 Node.js 环境中,http 模块的请求同样支持使用 AbortSignal 来中止请求。以下是一个简单的示例:
            
            
              javascript
              
              
            
          
          const http = require('http');
const { AbortController } = require('abort-controller');
const controller = new AbortController();
const req = http.get('http://example.com', { signal: controller.signal }, res => {
  res.on('data', chunk => {
    console.log(`Data: ${chunk}`);
  });
});
setTimeout(() => {
  controller.abort();
  console.log('Request aborted');
}, 5000);  // 5秒后中止请求
        这在处理长时间的 HTTP 请求时非常有用,尤其是需要通过中断请求来节省资源。
AbortSignal 的静态方法
AbortSignal 还提供了两个静态方法,timeout 和 any,可以增强中止操作的灵活性:
AbortSignal.timeout(duration):可以设置一个自动超时的中止信号。
            
            
              javascript
              
              
            
          
          const signal = AbortSignal.timeout(5000);  // 5秒后自动中止
fetch('/api/data', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch request timed out');
    }
  });
        AbortSignal.any([signal1, signal2, ...]):允许组合多个中止信号,任何一个信号触发中止,都会终止任务。
            
            
              javascript
              
              
            
          
          const controller1 = new AbortController();
const controller2 = new AbortController();
const signal = AbortSignal.any([controller1.signal, controller2.signal]);
fetch('/api/data', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch request aborted by one of the signals');
    }
  });
// 任意一个 controller 调用 abort 都会中止 fetch 请求
controller1.abort();
        取消流操作
在现代 JavaScript 中,AbortController 也可以用于中止可写流(WritableStream)的操作。在处理流式数据时,这种中止机制非常重要,可以有效管理数据传输和资源消耗:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
const stream = new WritableStream({
  write(chunk, controller) {
    // 写入逻辑
  },
  abort(reason) {
    console.log('Stream aborted:', reason);
  }
}, { signal: controller.signal });
// 中止流操作
controller.abort('Manual abort');
        中止任何逻辑
AbortController 不仅限于终止网络请求,还可以在任意可中止的逻辑中使用。比如,我们可以在 ORM 事务处理中使用它:
            
            
              javascript
              
              
            
          
          async function performTransaction() {
  const controller = new AbortController();
  try {
    await startTransaction({ signal: controller.signal });
    // 事务逻辑
  } catch (error) {
    if (error.name === 'AbortError') {
      console.log('Transaction aborted');
    }
  }
  controller.abort();
}
        中止错误处理
当中止事件发生时,AbortSignal 会附带一个 AbortError,开发者可以根据不同的中止原因进行错误处理。例如,我们可以传递一个自定义的中止原因并在错误处理中使用:
            
            
              javascript
              
              
            
          
          const controller = new AbortController();
controller.abort('Operation was manually cancelled');
fetch('/api/data', { signal: controller.signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch aborted with reason:', err.message);  // 输出自定义原因
    }
  });
        兼容性
AbortController 具有良好的兼容性,几乎所有现代浏览器都支持它。此外,Node.js 也通过实验性功能实现了对 AbortController 的支持。它已经成为开发者工具箱中不可或缺的部分。
总结
AbortController 是一个非常有用且灵活的 API,适用于多种异步操作的中止和管理。无论是取消 HTTP 请求、事件监听,还是流式数据处理,AbortController 都能为开发者提供精细的控制能力。通过合理使用它,我们可以更好地优化代码的健壮性和用户体验。