在前端开发中,我们经常遇到数据未准备好就要使用 的情况。例如,在微前端架构下,我们需要等 props.parentRoute
变成有效值后再执行渲染逻辑。然而,不少开发者在等待这个值时,会直接使用递归 ,但不加思考的递归可能导致页面卡死、性能浪费、甚至内存泄漏。
js
async function mount (props) {
await waitForParentRoute(props)
render(props) // 执行相关逻辑
}
今天,我们就来深入剖析递归等待 props.parentRoute
的各种方式,看看哪些方法稳妥,哪些方法要慎用,并带你找到最优解!🚀
1. 初学者常犯的坑:同步递归
代码示例:
js
function waitForParentRoute(props) {
if (props.parentRoute) {
console.log('parentRoute 获取成功', props.parentRoute);
return;
}
return waitForParentRoute(props); // 直接递归调用
}
结果:
- 如果
props.parentRoute
一直是undefined
,这个函数会无限递归 ,导致栈溢出(Maximum call stack size exceeded)。 - 页面卡死,浏览器崩溃。
💣 不推荐!这种同步递归方式在异步数据获取场景中极其危险!
2. while 循环:比同步递归更危险
有些开发者会尝试 while
来等待:
js
function waitForParentRoute(props) {
while (!props.parentRoute) {
// 等待 parentRoute 变为可用
}
console.log('parentRoute 获取成功', props.parentRoute);
}
结果:
- 由于
while
是同步阻塞的,它会疯狂占用 CPU,导致浏览器无法响应其他任务。 - 页面直接卡死,用户无法进行任何操作!
🔥🔥 绝对不要使用 while
来等待异步数据!
3. setTimeout 递归:安全但可能有延迟
代码示例:
js
function waitForParentRoute(props) {
if (props.parentRoute) {
console.log('parentRoute 获取成功', props.parentRoute);
return;
}
setTimeout(() => waitForParentRoute(props), 50);
}
优点:
✅ 不会卡死主线程 ,因为 setTimeout
是异步的。
✅ 浏览器仍然能响应其他任务,不会影响用户体验。
缺点:
⏳ 存在一定延迟 (取决于 setTimeout
的时间间隔)。
⏳ 可能需要多次执行,效率不如事件监听方式。
👉👉 适用于大部分场景,但仍然可以优化!
4. 事件驱动:MutationObserver 监听变化
如果 props.parentRoute
是绑定在某个 DOM 或对象上的 ,可以使用 MutationObserver
来监听它的变化:
js
function waitForParentRoute(props, callback) {
if (props.parentRoute) {
callback(props.parentRoute);
return;
}
const observer = new MutationObserver(() => {
if (props.parentRoute) {
callback(props.parentRoute);
observer.disconnect(); // 监听到值后立即停止监听,防止性能浪费
}
});
observer.observe(document, { subtree: true, childList: true });
}
优点:
✅ 高效、无延迟 ,一旦 parentRoute
变化就立即触发。
✅ 不占用 CPU 资源,只在变化时执行逻辑。
缺点:
⚠ 只能监听 DOM 变化 ,如果 props.parentRoute
不是 DOM 相关属性,就无法使用。
👉👉 适用于 parentRoute
由 DOM 绑定的情况!
5. Promise + 轮询:最推荐的方式!
如果 props.parentRoute
是普通变量 ,那么最好的方式是用 Promise
结合 setTimeout
进行非阻塞的轮询:
js
function waitForParentRoute(props, interval = 50, timeout = 5000) {
return new Promise((resolve, reject) => {
const start = Date.now();
const check = () => {
if (props.parentRoute) {
resolve(props.parentRoute);
} else if (Date.now() - start > timeout) {
reject(new Error('等待超时'));
} else {
setTimeout(check, interval);
}
};
check();
});
}
优点:
✅ 不会阻塞 UI ,setTimeout
让轮询在后台进行。
✅ 支持超时机制 ,防止死循环。
✅ 代码清晰,易于维护。
缺点:
⏳ 仍然有一点点延迟 (取决于 interval
设定的轮询时间)。
🎯🎯 推荐指数:⭐⭐⭐⭐⭐(最通用的方案!)
性能对比
方案 | 是否阻塞 UI | 是否高效 | 是否安全 | 适用场景 |
---|---|---|---|---|
同步递归 | 🚨 阻塞 | ❌ 效率极低 | ❌ 栈溢出风险 | 不推荐 |
while | 🚨 阻塞 | ❌ CPU 占满 | ❌ 页面卡死 | 禁用 |
setTimeout 递归 | ✅ 非阻塞 | ⏳ 可能有延迟 | ✅ 安全 | 适用于大部分场景 |
MutationObserver | ✅ 非阻塞 | ✅ 高效 | ✅ 适用于 DOM 变化 | 适用于监听 DOM |
Promise 轮询 | ✅ 非阻塞 | ✅ 高效 | ✅ 安全 | 最推荐 |
总结
- 不要使用同步递归和 while!它们会让你的页面直接卡死!
- setTimeout 递归 是简单可行的方案,但存在轻微延迟。
- MutationObserver 适用于DOM 变化监听。
- Promise 轮询 是最推荐的通用方案,既不会阻塞 UI ,又支持超时控制。
你的项目里是怎么处理类似的异步等待问题的?你有没有更优雅的方式?欢迎在评论区分享你的见解!