如何阻止特定Promise内部调用指定函数 避免JavaScript库出现无限循环
解决方案
方案1:使用AsyncLocalStorage(推荐)
现代浏览器(Chrome 89+、Firefox 88+、Safari 15.4+)和Node.js都内置了AsyncLocalStorage API,专门用于跨异步调用链传递上下文,完全不受await、定时器、Promise回调的影响,完美适配你的场景。
实现示例:
// Node.js环境引入 import { AsyncLocalStorage } from 'node:async_hooks'; // 低版本浏览器可以引入core-js的polyfill实现兼容 const asyncLocalStorage = new AsyncLocalStorage(); async function fireUserCallbacks() { // 在当前异步执行链内存入阻止调用标记,整个异步上下文都可以读取到 return asyncLocalStorage.run(true, async () => { // 执行用户的异步回调 await new Promise(r => setTimeout(r, 1000)); }); } function foo() { if (asyncLocalStorage.getStore() === true) { throw new Error('禁止在回调内部调用foo,请使用专门的递归处理函数'); } // 原有foo逻辑 }
验证效果符合预期:
foo(); // 外部直接调用正常执行 fireUserCallbacks(); foo(); // 不会被误拦截 fireUserCallbacks().then(() => foo()); // 回调执行完毕后调用也正常
方案2:适配现有资源加载逻辑的改造方案
你现有代码已经实现了RecursionTracker机制,只需要搭配AsyncLocalStorage做一层校验即可:
- 首先修改
loadAsset方法,新增调用校验:
async loadAsset(assetId, recursionTracker = null) { // 新增校验逻辑 if (recursionTracker === null) { const activeTracker = asyncLocalStorage.getStore(); if (activeTracker) { throw new Error('禁止在generateAsset内部直接调用loadAsset,请使用recursionTracker.loadAsset处理嵌套资源加载'); } } // 原有逻辑不变 // ... }
- 调用
generateAsset前存入当前的跟踪器实例:
const assetData = await asyncLocalStorage.run(recursionTracker, () => asset.generateAsset(recursionTracker));
用户在generateAsset内部如果忘记用提供的递归处理方法,直接调用loadAsset会立刻触发警告,从根源上避免无限递归。
方案3:兼容低版本环境的计数方案
如果需要适配不支持AsyncLocalStorage的运行环境,可以用计数器替代布尔标记,仅适合fireUserCallbacks无并发执行的场景:
let preventFooCallCount = 0; async function fireUserCallbacks() { preventFooCallCount++; try { // 执行用户异步回调 await new Promise(r => setTimeout(r, 1000)); } finally { preventFooCallCount--; } } function foo() { if (preventFooCallCount > 0) throw new Error(); // 原有foo逻辑 }
内容的提问来源于stack exchange,提问作者Jespertheend
相关产品推荐
相关产品推荐

