使用 AbortController 作为移除事件监听器的替代方案

Avatar of Carter Li
Carter Li

DigitalOcean 提供适用于您旅程各个阶段的云产品。立即开始使用 200 美元的免费积分!

“可中止”获取的想法在 2017 年 AbortController 发布时诞生。这为我们提供了一种方法来放弃由 fetch() 启动的 API 请求——即使是多个调用——无论何时我们想要。

这是一个使用 AbortController 取消 fetch() 请求的超级简单示例

const controller = new AbortController();
const res = fetch('/', { signal: controller.signal });
controller.abort();
console.log(res); // => Promise(rejected): "DOMException: The user aborted a request"

当用于现代的 setTimeout 接口时,您真的可以看出它的价值。这样,在例如 10 秒后使获取超时变得非常简单

function timeout(duration, signal) {
  return new Promise((resolve, reject) => {
    const handle = setTimeout(resolve, duration);
    signal?.addEventListener('abort', e => {
      clearTimeout(handle);
      reject(new Error('aborted'));
    });
  });
}

// Usage
const controller = new AbortController();
const promise = timeout(10000, controller.signal);
controller.abort();
console.log(promise); // => Promise(rejected): "Error: aborted"

但最大的新闻是 addEventListener 现在接受 中止信号,从 Chrome 88 开始。这有什么好处呢?它可以用作 removeEventListener 的替代方案

const controller = new AbortController();
eventTarget.addEventListener('event-type', handler, { signal: controller.signal });
controller.abort();

比这更酷的是什么?嗯,因为 AbortController 能够一次中止多个可取消的请求,它简化了删除多个监听器的过程,一步到位。我已经发现它在拖放方面特别有用。

以下是我在没有 AbortController 的情况下如何编写拖放脚本,依赖两个 removeEventListener 实例来清除两个不同的事件

// With removeEventListener
el.addEventListener('mousedown', e => {
  if (e.buttons !== 1) return;

  const onMousemove = e => {
    if (e.buttons !== 1) return;
    /* work */
  }

  const onMouseup = e => {
    if (e.buttons & 1) return;
    window.removeEventListener('mousemove', onMousemove);
    window.removeEventListener('mouseup', onMouseup);
  }

  window.addEventListener('mousemove', onMousemove);
  window.addEventListener('mouseup', onMouseup); // Can’t use `once: true` here because we want to remove the event only when primary button is up
});

通过最新的更新,addEventListener 接受 signal 属性作为其第二个参数,允许我们调用 abort() 一次来停止所有事件监听器,当它们不再需要时

// With AbortController
el.addEventListener('mousedown', e => {
  if (e.buttons !== 1) return;

  const controller = new AbortController();

  window.addEventListener('mousemove', e => {
    if (e.buttons !== 1) return;
    /* work */
  }, { signal: controller.signal });

  window.addEventListener('mouseup', e => {
    if (e.buttons & 1) return;
    controller.abort();
  }, { signal: controller.signal });
});

再次,Chrome 88 目前是唯一一个 addEventListener 正式接受 AbortSignal 的地方。虽然其他主要浏览器,包括 Firefox 和 Safari,支持 AbortController,但将它的信号与 addEventListener 集成目前还不可行……并且没有迹象表明他们计划在这方面进行努力。也就是说,有一个 polyfill 可用