解释 JavaScript 中的 AbortController 和 AbortSignal 如何实现异步请求的取消。

大家好,欢迎来到今天的JavaScript异步取消讲座!我是你们的老朋友,今天咱们就来聊聊JavaScript里这对黄金搭档:AbortControllerAbortSignal,看看它们是如何优雅地取消异步请求的。

开场白:异步请求的那些糟心事儿

想象一下,你正兴致勃勃地向服务器请求一大堆数据,结果突然发现,哎呀,点错链接了!或者用户手速飞快,在请求完成之前就离开了页面。这时候,如果请求依然在那里默默执行,不仅浪费资源,还可能导致一些意想不到的问题,比如更新已经卸载的组件,引发错误。

所以,我们需要一种机制,能够随时喊停这些正在进行的异步操作,让它们别再白费力气,这就是AbortControllerAbortSignal这对CP登场的时候了。

正文:AbortController 和 AbortSignal 的原理与用法

AbortControllerAbortSignal 就像一个遥控器和开关,AbortController负责发出取消信号,AbortSignal负责接收并通知异步操作停止。它们协同工作,让我们可以灵活地控制异步请求的生命周期。

1. AbortController:取消的遥控器

AbortController 只有一个主要的方法和一个属性:

  • abort(): 这个方法就是你的“取消”按钮,调用它会触发 AbortSignal 上的 abort 事件。
  • signal: 这是一个只读属性,返回一个与 AbortController 关联的 AbortSignal 对象。这个 AbortSignal 对象会被传递给异步操作,让它知道是否应该停止。

简单来说,AbortController 就是用来生成和发送取消信号的。

2. AbortSignal:请求的监听器

AbortSignal 对象拥有以下属性和方法:

  • aborted: 一个只读属性,如果 AbortSignal 已经接收到 abort 信号,则返回 true,否则返回 false
  • reason: 一个只读属性,返回 abort() 方法传递的可选取消原因。如果没有提供原因,则返回 undefined
  • addEventListener('abort', callback): 监听 abort 事件,当接收到取消信号时,执行回调函数。
  • removeEventListener('abort', callback): 移除 abort 事件的监听器。
  • throwIfAborted(): 如果 AbortSignal 已经中止,则抛出一个 DOMException 异常。

AbortSignal 就像一个监听器,默默地等待 AbortController 发出的取消信号,一旦接收到信号,它就会通知相关的异步操作停止。

3. 如何使用 AbortController 和 AbortSignal 取消请求?

让我们通过一些例子来深入了解如何使用 AbortControllerAbortSignal 取消请求。

例子 1:取消 fetch 请求

fetch API 原生支持 AbortSignal,我们可以将 AbortSignal 对象传递给 fetch 函数的 options 参数,让 fetch 请求可以被取消。

const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  })
  .then(data => {
    console.log('Data received:', data);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted');
    } else {
      console.error('Fetch error:', error);
    }
  });

// 在某个时刻取消请求
setTimeout(() => {
  controller.abort();
  console.log('Request aborted!');
}, 2000); // 2秒后取消请求

在这个例子中,我们创建了一个 AbortController 和一个 AbortSignal。然后,我们将 signal 传递给 fetch 函数。如果在 2 秒后,我们调用 controller.abort()fetch 请求就会被取消,并且 catch 块中的 error.name 会是 'AbortError'

例子 2:取消 XMLHttpRequest 请求

XMLHttpRequest (XHR) 也可以通过 AbortSignal 来取消。

const controller = new AbortController();
const signal = controller.signal;

const xhr = new XMLHttpRequest();

xhr.open('GET', 'https://api.example.com/data');

signal.addEventListener('abort', () => {
  xhr.abort();
  console.log('XHR aborted');
});

xhr.onload = () => {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('Data received:', xhr.responseText);
  } else {
    console.error('XHR error:', xhr.statusText);
  }
};

xhr.onerror = () => {
  console.error('XHR request failed');
};

xhr.send();

// 在某个时刻取消请求
setTimeout(() => {
  controller.abort();
  console.log('Request aborted!');
}, 2000); // 2秒后取消请求

在这个例子中,我们创建了一个 XMLHttpRequest 对象,并使用 addEventListener 监听 AbortSignalabort 事件。当 abort 事件被触发时,我们调用 xhr.abort() 来取消请求。

例子 3:取消自定义的异步操作

AbortControllerAbortSignal 不仅仅可以用于取消 fetchXMLHttpRequest 请求,还可以用于取消任何自定义的异步操作。

function myAsyncFunction(signal) {
  return new Promise((resolve, reject) => {
    // 检查是否已经中止
    if (signal.aborted) {
      reject(new Error('Aborted before starting'));
      return;
    }

    let timerId;

    // 监听 abort 事件
    signal.addEventListener('abort', () => {
      clearTimeout(timerId);
      reject(new Error('Aborted'));
      console.log('My async function aborted');
    });

    // 模拟异步操作
    timerId = setTimeout(() => {
      resolve('Async operation completed');
    }, 3000);
  });
}

const controller = new AbortController();
const signal = controller.signal;

myAsyncFunction(signal)
  .then(result => {
    console.log(result);
  })
  .catch(error => {
    console.error(error.message);
  });

// 在某个时刻取消请求
setTimeout(() => {
  controller.abort();
  console.log('Request aborted!');
}, 2000); // 2秒后取消请求

在这个例子中,我们定义了一个 myAsyncFunction 函数,它接受一个 AbortSignal 对象作为参数。在函数内部,我们首先检查 signal.aborted 属性,如果已经中止,则立即拒绝 Promise。然后,我们监听 AbortSignalabort 事件,当事件被触发时,我们清除定时器并拒绝 Promise。

4. AbortSignal.reason

AbortSignal.reason 属性允许你传递一个取消的原因,这在调试和日志记录时非常有用。

const controller = new AbortController();
const signal = controller.signal;

fetch('https://api.example.com/data', { signal })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('Fetch aborted with reason:', signal.reason);
    } else {
      console.error('Fetch error:', error);
    }
  });

// 在某个时刻取消请求
setTimeout(() => {
  controller.abort('Request timed out'); // 传递取消原因
  console.log('Request aborted!');
}, 2000); // 2秒后取消请求

在这个例子中,我们在调用 controller.abort() 时传递了一个字符串 'Request timed out' 作为取消原因。在 catch 块中,我们可以通过 signal.reason 访问这个原因。

5. AbortSignal.throwIfAborted()

AbortSignal.throwIfAborted() 方法可以用来在代码的任何地方检查 AbortSignal 是否已经中止。如果已经中止,它会抛出一个 DOMException 异常。

const controller = new AbortController();
const signal = controller.signal;

try {
  // 检查是否已经中止
  signal.throwIfAborted();

  // 执行一些操作
  console.log('Performing some operations...');

  // 模拟一些耗时操作
  setTimeout(() => {
    console.log('Operations completed');
  }, 3000);
} catch (error) {
  if (error.name === 'AbortError') {
    console.log('Operation aborted');
  } else {
    console.error('Error:', error);
  }
}

// 在某个时刻取消请求
setTimeout(() => {
  controller.abort();
  console.log('Request aborted!');
}, 2000); // 2秒后取消请求

在这个例子中,我们使用 signal.throwIfAborted()try 块中检查 AbortSignal 是否已经中止。如果在 2 秒后调用 controller.abort()throwIfAborted() 方法会抛出一个 DOMException 异常,然后 catch 块会捕获这个异常并打印 "Operation aborted"。

6. 最佳实践和注意事项

  • 及时清理资源: 在取消异步操作后,一定要及时清理相关的资源,比如取消定时器、移除事件监听器等,避免内存泄漏。
  • 错误处理: 在取消异步操作时,要正确处理错误,避免程序崩溃。
  • 避免过度取消: 不要过度取消异步操作,只有在真正需要取消时才取消。
  • 与框架集成: 如果使用框架(例如 React, Vue, Angular),要了解框架提供的取消异步操作的机制,并与 AbortControllerAbortSignal 结合使用。
  • 不要忘记传递 signal: 初学者经常犯的一个错误就是创建了 AbortControllerAbortSignal,但是忘记将 signal 传递给异步操作。

表格总结

特性 AbortController AbortSignal
作用 生成和发送取消信号 接收取消信号并通知异步操作停止
主要方法 abort() addEventListener('abort', callback), removeEventListener('abort', callback), throwIfAborted()
主要属性 signal aborted, reason
用途 取消 fetch 请求,取消 XMLHttpRequest 请求,取消自定义的异步操作 用于传递给异步操作(例如 fetch),监听 abort 事件,检查是否已经中止
应用场景 用户离开页面,用户取消操作,请求超时等 在异步操作中,检查是否已经接收到取消信号,并执行相应的清理工作
注意事项 创建 AbortController 后,一定要将 signal 传递给异步操作 在异步操作中,一定要及时清理资源,避免内存泄漏
替代方案(老旧) 手动维护状态标志,使用 Promise.reject() 取消 Promise

结束语:异步世界的秩序维护者

AbortControllerAbortSignal 就像异步世界的秩序维护者,它们让我们可以更加精细地控制异步操作,避免资源浪费,提高用户体验。掌握它们,你就能在异步编程的道路上走得更远,更稳。

希望今天的讲座对你有所帮助!下次再见!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注