ResizeObserver - 超出循环限制
ResizeObserver - loop limit exceeded
大约两个月前,我们开始使用 Rollbar 来通知我们 Web 应用程序中的各种错误。从那时起,我们就偶尔会遇到错误:
ResizeObserver loop limit exceeded
让我感到困惑的是我们没有使用 ResizeObserver
,我调查了唯一一个我认为可能是罪魁祸首的插件,即:
但它似乎也没有使用 ResizeObserver
。
同样令人困惑的是,这些错误消息自 1 月以来一直出现,但 ResizeObserver
支持最近才添加到 Chrome 65。
出现此错误的浏览器版本是:
- Chrome: 63.0.3239(超出 ResizeObserver 循环限制)
- Chrome: 64.0.3282(超出 ResizeObserver 循环限制)
- 边缘:14.14393(安全错误)
- 边缘:15.15063(安全错误)
所以我想知道这是否可能是浏览器错误?或者可能是一个实际上与 ResizeObserver
?
无关的错误
您可以安全地忽略此错误。
其中一位规范作者在对您的问题的评论中写道,但这不是答案,并且评论中不清楚答案是否确实是该线程中最重要的答案,也是让我印象深刻的答案在我们的哨兵日志中忽略它很舒服。
This error means that ResizeObserver was not able to deliver all observations within a single animation frame. It is benign (your site will not break). – Aleksandar Totic Apr 15 at 3:14
规范存储库中也有一些 related issues。
这是一个老问题,但它仍然可能对某些人有所帮助。您可以通过将回调包装在 requestAnimationFrame
中来避免此错误。
例如:
const resizeObserver = new ResizeObserver(entries => {
// We wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return;
}
// your code
});
});
我们遇到了同样的问题。我们发现 chrome 扩展是罪魁祸首。具体来说,loom chrome 扩展导致了错误(或者我们的代码与 loom 扩展的一些交互)。当我们禁用扩展程序时,我们的应用程序可以正常工作。
我建议禁用某些 extensions/addons 以查看是否其中之一可能导致错误。
如果您使用的是 Cypress 并且遇到了这个问题,您可以在 support/index.js 或 commands.ts
中使用以下代码在 Cypress 中安全地忽略它
const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopErrRe.test(err.message)) {
return false
}
})
大家可以关注一下讨论here。
由于 Cypress 维护者自己提出了这个解决方案,所以我相信这样做是安全的。
像
一样添加去抖动
new ResizeObserver(_.debounce(条目 => {}, 200);
为我修复了这个错误
摩卡用户:
下面的代码片段覆盖了 window.onerror 挂钩 mocha 安装并将错误转换为警告。
https://github.com/mochajs/mocha/blob/667e9a21c10649185e92b319006cea5eb8d61f31/browser-entry.js#L74
// ignore ResizeObserver loop limit exceeded
// this is ok in several scenarios according to
// https://github.com/WICG/resize-observer/issues/38
before(() => {
// called before any tests are run
const e = window.onerror;
window.onerror = function(err) {
if(err === 'ResizeObserver loop limit exceeded') {
console.warn('Ignored: ResizeObserver loop limit exceeded');
return false;
} else {
return e(...arguments);
}
}
});
不确定是否有更好的方法..
https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/resize_observer/resize_observer_controller.cc#L44-L45
https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/frame/local_frame_view.cc#L2211-L2212
查看源代码后,我的情况似乎是在调用 NotifyResizeObservers 函数时出现问题,并且没有注册的观察者。
如果没有观察者,GatherObservations 函数将 return min_depth 4096,在这种情况下,我们将收到“超出 ResizeObserver 循环限制”错误。
我的解决方法是让观察者贯穿页面的整个生命周期。
在我的例子中,“ResizeObserver - 超出循环限制”问题是由于 window.addEventListener("resize"
和 React 的 React.useState
.
而触发的
详细来说,我正在研究名为 useWindowResize
的钩子,其中的用例是这样的 const [windowWidth, windowHeight] = useWindowResize();
。
代码通过 useEffect.
对 windowWidth/windowHeight 更改作出反应
React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setWidgetHeight(h),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
所以任何浏览器 window 调整大小都会导致该问题。
我发现许多类似的问题是由于连接 old-javascript-world(DOM 操作,浏览器的事件)和new-javascript-world (React) 可能会被 setTimeout
解决,但我会尽可能避免它并称其为反模式。
所以我的解决方法是将 setter 方法包装到 setTimeout 函数中。
React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setTimeout(() => setWidgetHeight(h), 0),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
该错误可能值得调查。它可能表明您的代码中存在可以修复的问题。
在我们的例子中,观察到的元素大小调整触发了页面上的更改,这导致第一个元素的大小再次调整,这再次触发了页面上的更改,这再次导致了第一个元素的大小调整, ......你知道这是怎么结束的。
本质上,我们显然创建了一个无法放入单个动画帧的无限循环。我们通过使用 setTimeout()
阻止页面上的更改来打破它(尽管这并不完美,因为它可能会给用户带来一些闪烁)。
所以现在每次 ResizeObserver loop limit exceeded
出现在我们的 Sentry 中,我们都会将其视为有用的提示并尝试找出问题的原因。
Cypress 的一行解决方案。使用以下内容编辑文件 support/commands.js:
Cypress.on(
'uncaught:exception',
(err) => !err.message.includes('ResizeObserver loop limit exceeded')
);
设法在 React 中为我们的错误记录器设置解决这个问题。
Observer 错误传播到 window.onerror
错误处理程序,因此通过将原始 window.onerror
存储在 ref 中,您可以将其替换为不会为此特定异常抛出的自定义方法错误。允许其他错误正常传播。
确保在 useEffect
清理中重新连接原来的 onerror
。
const defaultOnErrorFn = useRef(window.onerror);
useEffect(() => {
window.onerror = (...args) => {
if (args[0] === 'ResizeObserver loop limit exceeded') {
return true;
} else {
defaultOnErrorFn.current && defaultOnErrorFn.current(...args);
}
};
return () => {
window.onerror = defaultOnErrorFn.current;
};
}, []);
大约两个月前,我们开始使用 Rollbar 来通知我们 Web 应用程序中的各种错误。从那时起,我们就偶尔会遇到错误:
ResizeObserver loop limit exceeded
让我感到困惑的是我们没有使用 ResizeObserver
,我调查了唯一一个我认为可能是罪魁祸首的插件,即:
但它似乎也没有使用 ResizeObserver
。
同样令人困惑的是,这些错误消息自 1 月以来一直出现,但 ResizeObserver
支持最近才添加到 Chrome 65。
出现此错误的浏览器版本是:
- Chrome: 63.0.3239(超出 ResizeObserver 循环限制)
- Chrome: 64.0.3282(超出 ResizeObserver 循环限制)
- 边缘:14.14393(安全错误)
- 边缘:15.15063(安全错误)
所以我想知道这是否可能是浏览器错误?或者可能是一个实际上与 ResizeObserver
?
您可以安全地忽略此错误。
其中一位规范作者在对您的问题的评论中写道,但这不是答案,并且评论中不清楚答案是否确实是该线程中最重要的答案,也是让我印象深刻的答案在我们的哨兵日志中忽略它很舒服。
This error means that ResizeObserver was not able to deliver all observations within a single animation frame. It is benign (your site will not break). – Aleksandar Totic Apr 15 at 3:14
规范存储库中也有一些 related issues。
这是一个老问题,但它仍然可能对某些人有所帮助。您可以通过将回调包装在 requestAnimationFrame
中来避免此错误。
例如:
const resizeObserver = new ResizeObserver(entries => {
// We wrap it in requestAnimationFrame to avoid this error - ResizeObserver loop limit exceeded
window.requestAnimationFrame(() => {
if (!Array.isArray(entries) || !entries.length) {
return;
}
// your code
});
});
我们遇到了同样的问题。我们发现 chrome 扩展是罪魁祸首。具体来说,loom chrome 扩展导致了错误(或者我们的代码与 loom 扩展的一些交互)。当我们禁用扩展程序时,我们的应用程序可以正常工作。
我建议禁用某些 extensions/addons 以查看是否其中之一可能导致错误。
如果您使用的是 Cypress 并且遇到了这个问题,您可以在 support/index.js 或 commands.ts
中使用以下代码在 Cypress 中安全地忽略它const resizeObserverLoopErrRe = /^[^(ResizeObserver loop limit exceeded)]/
Cypress.on('uncaught:exception', (err) => {
/* returning false here prevents Cypress from failing the test */
if (resizeObserverLoopErrRe.test(err.message)) {
return false
}
})
大家可以关注一下讨论here。 由于 Cypress 维护者自己提出了这个解决方案,所以我相信这样做是安全的。
像
一样添加去抖动new ResizeObserver(_.debounce(条目 => {}, 200);
为我修复了这个错误
摩卡用户:
下面的代码片段覆盖了 window.onerror 挂钩 mocha 安装并将错误转换为警告。 https://github.com/mochajs/mocha/blob/667e9a21c10649185e92b319006cea5eb8d61f31/browser-entry.js#L74
// ignore ResizeObserver loop limit exceeded
// this is ok in several scenarios according to
// https://github.com/WICG/resize-observer/issues/38
before(() => {
// called before any tests are run
const e = window.onerror;
window.onerror = function(err) {
if(err === 'ResizeObserver loop limit exceeded') {
console.warn('Ignored: ResizeObserver loop limit exceeded');
return false;
} else {
return e(...arguments);
}
}
});
不确定是否有更好的方法..
https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/resize_observer/resize_observer_controller.cc#L44-L45 https://github1s.com/chromium/chromium/blob/master/third_party/blink/renderer/core/frame/local_frame_view.cc#L2211-L2212
查看源代码后,我的情况似乎是在调用 NotifyResizeObservers 函数时出现问题,并且没有注册的观察者。
如果没有观察者,GatherObservations 函数将 return min_depth 4096,在这种情况下,我们将收到“超出 ResizeObserver 循环限制”错误。
我的解决方法是让观察者贯穿页面的整个生命周期。
在我的例子中,“ResizeObserver - 超出循环限制”问题是由于 window.addEventListener("resize"
和 React 的 React.useState
.
详细来说,我正在研究名为 useWindowResize
的钩子,其中的用例是这样的 const [windowWidth, windowHeight] = useWindowResize();
。
代码通过 useEffect.
对 windowWidth/windowHeight 更改作出反应React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setWidgetHeight(h),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
所以任何浏览器 window 调整大小都会导致该问题。
我发现许多类似的问题是由于连接 old-javascript-world(DOM 操作,浏览器的事件)和new-javascript-world (React) 可能会被 setTimeout
解决,但我会尽可能避免它并称其为反模式。
所以我的解决方法是将 setter 方法包装到 setTimeout 函数中。
React.useEffect(() => {
ViewportService.dynamicDimensionControlledBy(
"height",
{ windowWidth, windowHeight },
widgetModalRef.current,
{ bottom: chartTitleHeight },
false,
({ h }) => setTimeout(() => setWidgetHeight(h), 0),
);
}, [windowWidth, windowHeight, widgetModalRef, chartTitleHeight]);
该错误可能值得调查。它可能表明您的代码中存在可以修复的问题。
在我们的例子中,观察到的元素大小调整触发了页面上的更改,这导致第一个元素的大小再次调整,这再次触发了页面上的更改,这再次导致了第一个元素的大小调整, ......你知道这是怎么结束的。
本质上,我们显然创建了一个无法放入单个动画帧的无限循环。我们通过使用 setTimeout()
阻止页面上的更改来打破它(尽管这并不完美,因为它可能会给用户带来一些闪烁)。
所以现在每次 ResizeObserver loop limit exceeded
出现在我们的 Sentry 中,我们都会将其视为有用的提示并尝试找出问题的原因。
Cypress 的一行解决方案。使用以下内容编辑文件 support/commands.js:
Cypress.on(
'uncaught:exception',
(err) => !err.message.includes('ResizeObserver loop limit exceeded')
);
设法在 React 中为我们的错误记录器设置解决这个问题。
Observer 错误传播到 window.onerror
错误处理程序,因此通过将原始 window.onerror
存储在 ref 中,您可以将其替换为不会为此特定异常抛出的自定义方法错误。允许其他错误正常传播。
确保在 useEffect
清理中重新连接原来的 onerror
。
const defaultOnErrorFn = useRef(window.onerror);
useEffect(() => {
window.onerror = (...args) => {
if (args[0] === 'ResizeObserver loop limit exceeded') {
return true;
} else {
defaultOnErrorFn.current && defaultOnErrorFn.current(...args);
}
};
return () => {
window.onerror = defaultOnErrorFn.current;
};
}, []);