JS `AbortController` 实现多个异步请求的统一取消

各位靓仔靓女,今天咱们来聊聊JS里一个挺有意思的家伙——AbortController,以及它如何优雅地搞定多个异步请求的统一取消。这玩意儿就像个遥控器,能让你随时喊停那些正在磨磨唧唧跑着的请求,避免资源浪费,提高用户体验。

一、开场:为啥我们需要这玩意儿?

想象一下,你正在做一个搜索框,用户每输入一个字,就发起一次搜索请求。如果用户输入速度很快,那之前的请求可能还在路上,新的请求就又发出去了。这时候,之前的请求结果已经没用了,但它们还在占用带宽,消耗服务器资源。这时候AbortController就派上大用场了!

或者,你正在做一个分页功能,用户快速点击下一页,上一页的数据还没加载完成,新的请求又发出去了。这时候,之前的请求也变得多余了。

总之,在需要频繁发起异步请求,且旧请求可能失效的场景下,AbortController能帮你省钱省力,让你的应用更“丝滑”。

二、主角登场:AbortController是个啥?

AbortController是Web API提供的一个接口,用于控制和取消Web请求,例如fetchXMLHttpRequest。它主要包含两个关键部分:

  • AbortController.signal 一个AbortSignal对象,用于传递取消信号给请求。

  • AbortController.abort() 一个方法,用于触发取消信号。

简单来说,AbortController负责发出“停止”的信号,而AbortSignal负责把这个信号传递给正在运行的请求。

三、AbortController的基本用法:单枪匹马取消一个请求

咱们先从最简单的场景开始,用AbortController取消一个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);
  })
  .catch(error => {
    if (error.name === 'AbortError') {
      console.log('请求被取消了!');
    } else {
      console.error('发生错误:', error);
    }
  });

// 5秒后取消请求
setTimeout(() => {
  controller.abort();
  console.log('请求已取消!');
}, 5000);

这段代码做了啥?

  1. 创建了一个AbortController实例。
  2. AbortController实例中获取了AbortSignal对象。
  3. fetch请求的配置中,将signal属性设置为AbortSignal对象。
  4. 5秒后,调用AbortController.abort()方法,触发取消信号。
  5. catch块中,判断错误是否是AbortError,如果是,说明请求是被取消的。

关键点在于,fetch函数的配置项里,通过signal属性接收了AbortSignal对象。这样,当AbortController.abort()被调用时,fetch请求就会收到取消信号,并抛出一个AbortError

四、更进一步:多个请求,一个遥控器

现在,咱们来挑战一下,如何用一个AbortController控制多个fetch请求。

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

const urls = [
  'https://api.example.com/data1',
  'https://api.example.com/data2',
  'https://api.example.com/data3'
];

const promises = urls.map(url =>
  fetch(url, { signal })
    .then(response => {
      if (!response.ok) {
        throw new Error(`HTTP error! status: ${response.status}`);
      }
      return response.json();
    })
    .catch(error => {
      if (error.name === 'AbortError') {
        console.log(`请求 ${url} 被取消了!`);
      } else {
        console.error(`请求 ${url} 发生错误:`, error);
      }
      // 关键:重新抛出错误,以便Promise.allSettled处理
      throw error;
    })
);

Promise.allSettled(promises) // 使用 Promise.allSettled 防止一个请求失败导致全部失败
  .then(results => {
    results.forEach((result, index) => {
      if (result.status === 'fulfilled') {
        console.log(`请求 ${urls[index]} 成功:`, result.value);
      } else {
        console.log(`请求 ${urls[index]} 失败:`, result.reason);
      }
    });
  });

// 5秒后取消所有请求
setTimeout(() => {
  controller.abort();
  console.log('所有请求已取消!');
}, 5000);

这段代码做了这些事情:

  1. 创建了一个AbortController实例。
  2. 定义了一个包含多个URL的数组。
  3. 使用map方法,为每个URL创建一个fetch请求,并将同一个AbortSignal对象传递给每个请求。
  4. 使用Promise.allSettled方法,等待所有请求完成(无论成功还是失败)。
  5. 5秒后,调用AbortController.abort()方法,取消所有请求。

这里最关键的地方是,多个fetch请求都使用了同一个AbortSignal对象。这样,当AbortController.abort()被调用时,所有请求都会收到取消信号。

五、实战演练:搜索框的优化

咱们回到最开始的例子,用AbortController来优化搜索框的体验。

<!DOCTYPE html>
<html>
<head>
  <title>搜索框优化</title>
</head>
<body>
  <input type="text" id="searchInput" placeholder="请输入关键词">
  <ul id="searchResults"></ul>

  <script>
    const searchInput = document.getElementById('searchInput');
    const searchResults = document.getElementById('searchResults');
    let controller = null; // 用于保存当前的 AbortController 实例

    searchInput.addEventListener('input', async (event) => {
      const keyword = event.target.value;

      // 如果有正在进行的请求,先取消
      if (controller) {
        controller.abort();
      }

      // 创建新的 AbortController 实例
      controller = new AbortController();
      const signal = controller.signal;

      try {
        const response = await fetch(`https://api.example.com/search?q=${keyword}`, { signal }); // 替换为你的搜索API
        if (!response.ok) {
          throw new Error(`HTTP error! status: ${response.status}`);
        }
        const data = await response.json();

        // 显示搜索结果
        searchResults.innerHTML = '';
        data.forEach(item => {
          const li = document.createElement('li');
          li.textContent = item.title;
          searchResults.appendChild(li);
        });
      } catch (error) {
        if (error.name === 'AbortError') {
          console.log('搜索请求被取消了!');
        } else {
          console.error('搜索发生错误:', error);
          searchResults.innerHTML = '<li>搜索出错,请稍后重试</li>';
        }
      }
    });
  </script>
</body>
</html>

这段代码的关键在于:

  1. 每次用户输入时,先判断是否有正在进行的请求。
  2. 如果有,就调用controller.abort()取消之前的请求。
  3. 创建一个新的AbortController实例,并将其赋值给controller变量。
  4. 发起新的搜索请求,并将新的AbortSignal对象传递给请求。

这样,每次用户输入时,都会取消之前的请求,保证只有最新的请求才会生效。

六、AbortControllerXMLHttpRequest

AbortController不仅可以用于fetch,也可以用于XMLHttpRequest

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

const xhr = new XMLHttpRequest();
xhr.open('GET', 'https://api.example.com/data');
xhr.signal = signal; // 将 AbortSignal 赋给 xhr.signal

xhr.onload = () => {
  if (xhr.status >= 200 && xhr.status < 300) {
    console.log('数据加载成功:', xhr.responseText);
  } else {
    console.error('请求失败:', xhr.status, xhr.statusText);
  }
};

xhr.onerror = () => {
  console.error('请求发生错误');
};

xhr.onabort = () => {
  console.log('请求被取消了!');
};

xhr.send();

// 5秒后取消请求
setTimeout(() => {
  controller.abort();
  console.log('请求已取消!');
}, 5000);

使用方法类似,将AbortSignal对象赋值给XMLHttpRequestsignal属性即可。 xhr.onabort事件会在请求被取消时触发。

七、注意事项和最佳实践

  • 错误处理: 一定要处理AbortError,否则可能会导致程序崩溃。
  • 资源释放: 在请求被取消后,及时释放相关资源,例如清理定时器,取消事件监听等。
  • Promise.allSettled 当需要并发发起多个请求时,使用Promise.allSettled可以防止一个请求失败导致整个流程中断。
  • 兼容性: AbortController的兼容性良好,主流浏览器都支持。如果需要兼容旧版本浏览器,可以使用polyfill。

八、表格总结:AbortController的属性和方法

属性/方法 描述
AbortController.signal 返回一个 AbortSignal 对象,可以传递给 fetchXMLHttpRequest
AbortController.abort() 触发取消信号,所有与该 AbortController 关联的请求都会被取消。
AbortSignal.aborted 只读属性,表示 AbortSignal 是否已被触发取消。
AbortSignal.onabort 事件处理函数,当 AbortSignal 被触发取消时执行。

九、灵魂拷问:AbortController还有哪些应用场景?

除了上面提到的搜索框和分页功能,AbortController还可以用于:

  • 文件上传: 取消正在上传的文件。
  • 视频播放: 取消正在加载的视频。
  • 数据同步: 取消正在进行的数据同步。
  • 复杂的用户界面: 在用户切换标签页或关闭窗口时,取消所有正在进行的请求。

十、总结:AbortController,你的异步请求“暂停键”

AbortController是一个非常实用的工具,可以让你更好地控制异步请求,提高应用的性能和用户体验。掌握了它,你就拥有了一个强大的“暂停键”,可以随时喊停那些磨磨唧唧的请求,让你的代码更加优雅、高效。

希望今天的分享对大家有所帮助! 记得多练习,才能真正掌握AbortController的精髓哦!下课!

发表回复

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