探索 JavaScript 中的 AbortController API:不仅仅是中断 HTTP 请求

发布于:2024-10-17 ⋅ 阅读:(11) ⋅ 点赞:(0)

在 JavaScript 中处理异步操作时,通常需要灵活的控制机制来中止任务。AbortController API 提供了一个强大且通用的方式来终止异步操作,不仅可以中断 HTTP 请求,还可以应用于多种异步任务,如事件监听、流操作等。在本文中,我们将详细探讨 AbortController 的使用场景、AbortSignal 的静态方法、事件处理中的中止机制,以及在实际开发中的一些最佳实践。

AbortController 简介

AbortController 是 JavaScript 中的全局类,专门用于管理和中止异步操作。通过创建一个 AbortController 实例,我们可以获取两个核心功能:

  1. signal 属性:用于监控是否有中止信号发出,能够传递给异步操作,比如 fetch 请求或事件监听器。
  2. .abort() 方法:可以随时手动触发中止信号,进而取消异步操作。

基本用法如下:

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 的第三个参数,使得在中止时自动移除事件监听器:

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,可以在请求发出后随时中断:

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 取消请求:

const controller = new AbortController();

axios.get('/foo/bar', {
   signal: controller.signal
}).then(function(response) {
   //...
});
// 取消请求
controller.abort()
  • 当然还可以使用 CancelToken 取消一个请求,但需要注意的是此 API 从 v0.22.0 开始已被弃用,不应在新项目中使用。
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 来中止请求。以下是一个简单的示例:

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 还提供了两个静态方法,timeoutany,可以增强中止操作的灵活性:

  1. AbortSignal.timeout(duration):可以设置一个自动超时的中止信号。
const signal = AbortSignal.timeout(5000);  // 5秒后自动中止

fetch('/api/data', { signal })
  .catch(err => {
    if (err.name === 'AbortError') {
      console.log('Fetch request timed out');
    }
  });
  1. AbortSignal.any([signal1, signal2, ...]):允许组合多个中止信号,任何一个信号触发中止,都会终止任务。
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)的操作。在处理流式数据时,这种中止机制非常重要,可以有效管理数据传输和资源消耗:

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 事务处理中使用它:

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,开发者可以根据不同的中止原因进行错误处理。例如,我们可以传递一个自定义的中止原因并在错误处理中使用:

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 都能为开发者提供精细的控制能力。通过合理使用它,我们可以更好地优化代码的健壮性和用户体验。