WeakMap+AbortController——优雅地取消请求

AbortController

AbortController可以用来取消fetch请求,MDN给出了简单的demo,就不在此处赘述了。

AbortController 接口表示一个控制器对象,允许你根据需要中止一个或多个 Web 请求。

你可以使用 AbortController() 构造函数创建一个新的 AbortController 对象。使用 AbortSignal 对象可以完成与异步操作的通信。

AbortController取消XHR

AbortController的缺点是无法直接取消XHR,但XHR对象本身就提供了abortapi,我们可以通过监听signal的abort事件,变相实现AbortController取消XHR。

arduino 复制代码
const xhr = new XMLHttpRequest();
xhr.open('GET', '/api/data');
xhr.send();

// 监听 signal
signal.addEventListener('abort', () => {
  xhr.abort(); // 使用 xhr 原生取消
});

axios的请求取消也是基于该思路实现的。

ini 复制代码
/ 创建控制器
const controller = new AbortController();
const signal = controller.signal;

// 发起请求,传入 signal
axios.get('/api/data', { signal })
  .then(response => {
   ...
  })
  .catch(error => {
    ...
  });

// 2 秒后取消请求
setTimeout(() => {
  controller.abort(); // 会触发上方的 catch 分支
}, 2000);

然而项目中请求取消与请求发送往往不在同一个文件中,因此需要缓存请求Promise实例和对应的AbortController实例,这也意味着开发不仅需要关注请求取消,还需要关注这两个实例的清理,以防止内存泄漏,这引入了额外的心智负担。

使用WeakMap缓存请求Promise和AbortController

为了减轻这部分心智负担,我倾向于在项目中使用WeakMap来缓存请求Promise和AbortController。一般来说,项目中不会额外缓存(引用)请求promise,请求完成后,只要该promise"不可达",GC便会回收该实例,对应的abortController也会因为"不可达"而被回收掉。 参考代码如下:

javascript 复制代码
// api.ts
const abortMap = new WeakMap();
window.abortMap = abortMap; //测试gc

const putSth = () => {
  const controller = new AbortController();
  const req = request({ url: "/api/clear", signal: controller.signal }).catch(
    (e) => console.error(e)
  );
  req.t = 1; //测试gc
  abortMap.set(req, controller);
  return req;
};
// component.ts
export function ClearTest() {
  const [visible, setVisible] = useState(true);
  return (
    <div>
      <button
        onClick={() => {
          setVisible((v) => !v);
        }}
      >
        toggle
      </button>
      {visible && <Test></Test>}
    </div>
  );
}
function Test() {
  useEffect(() => {
    const req = putSth();
    return () => {
    //取消请求
      abortMap.get(req)?.abort("cancel");
    };
  }, []);
  return <div>test</div>;
}

内存观察

上面的demo中我加了一些调试代码。对组件进行多次挂载卸载测试,观察控制台可以看到每次请求取消后会打印cancel error观察abortMap,可以明显看到部分请求promise被回收了,程序符合预期:

但也有可能观察到abortMap中存在大量未被回收的promise:

我们可以手动清理垃圾后再观察

手动清理虽然会触发GC,但不一定会清理掉所有垃圾对象(分代垃圾回收)。

通过快照可以看到这些Promise实例的距离都是-,这意味着它们"不可达",如果不在闭包内,将来会被GC清理掉。

经过一段时间后,再次观察可以看到weakMap中所有请求Promise都被回收了:

如果有朋友发现weakMap中的Promise始终无法被回收掉,可能和控制台打印有关系,清理控制台关掉后再打开控制台,支持垃圾回收,来回几次,可能就会看到weakMap被清空了。

相关推荐
To_OC6 小时前
别再串行写 await 了,Promise.all 才是并行请求的正确打开方式
前端·javascript·promise
vipbic6 小时前
中后台越做越乱后,我用插件化把它救回来了
前端·vue.js
Hyyy6 小时前
Computer Use 适合做什么,不适合做什么——一次真实使用后的思考
前端
小和尚同志7 小时前
前端 AI 单元测试思考与落地
前端·人工智能·aigc
invicinble8 小时前
c端系统,其实更像一个信息展示平台
前端
李姆斯8 小时前
管理是否可以被完全量化
前端·产品经理·团队管理
名字还没想好☜9 小时前
Next.js 中间件实战:鉴权、重定向与 A/B 分流
开发语言·前端·javascript·中间件·react·next.js
广州灵眸科技有限公司9 小时前
瑞芯微RV1126B开发板(EASY-EAI-PI2) INI文件操作
java·前端·javascript·网络·人工智能
kaiking_g10 小时前
vue项目中,静态导入 vs 动态导入 详解
前端·javascript·vue.js
江华森10 小时前
Web 开发基础:HTTP 与 REST
前端·网络协议·http