ResizeObserver - 超出循环限制

ResizeObserver - loop limit exceeded

大约两个月前,我们开始使用 Rollbar 来通知我们 Web 应用程序中的各种错误。从那时起,我们就偶尔会遇到错误:

ResizeObserver loop limit exceeded

让我感到困惑的是我们没有使用 ResizeObserver,我调查了唯一一个我认为可能是罪魁祸首的插件,即:

Aurelia Resize

但它似乎也没有使用 ResizeObserver

同样令人困惑的是,这些错误消息自 1 月以来一直出现,但 ResizeObserver 支持最近才添加到 Chrome 65。

出现此错误的浏览器版本是:

所以我想知道这是否可能是浏览器错误?或者可能是一个实际上与 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;
  };
}, []);