12k
All articles

Was „ResizeObserver Loop Completed“-Fehler bedeuten

Verstehen Sie ResizeObserver-Schleifenfehler, warum der Browser Benachrichtigungen verzögert, wie Sie echte Layout-Schleifen erkennen und beide Meldungen sicher filtern.

OpenReplay Team
OpenReplay Team
Was „ResizeObserver Loop Completed“-Fehler bedeuten

„ResizeObserver loop completed with undelivered notifications“ ist ein Error-Event des Browsers und keine Exception aus Ihrem Code. Die Meldung besagt, dass einige Resize-Benachrichtigungen den aktuellen Frame verpasst haben und in den nächsten verschoben wurden. In den meisten Fällen können Sie sie bedenkenlos ignorieren.

Typischerweise taucht der Fehler im Error-Tracker als Hunderte von Events ohne aussagekräftigen Stack auf oder lässt einen Cypress-Lauf fehlschlagen, und oft kommt ResizeObserver in Ihrem eigenen Code nirgends vor. Dieser Artikel erklärt, was der Browser dabei tut, zeigt ein minimales Reproduktionsbeispiel und beschreibt, wie Sie harmloses Rauschen von einer echten Schleife unterscheiden. Am Ende steht ein Filter, der beide Varianten der Meldung erfasst.

Das Wichtigste in Kürze

  • Der Browser löst „ResizeObserver loop completed with undelivered notifications.“ als Error-Event auf window aus, wenn Resize-Benachrichtigungen bis zum nächsten Frame warten müssen.
  • Ältere Browserversionen meldeten denselben Zustand als „ResizeObserver loop limit exceeded“. Jeder Filter und jeder Test-Handler muss daher beide Meldungen erfassen.
  • Der Schutzmechanismus verhindert, dass der Browser einfriert. Die Schleife selbst stoppt er nicht: Ein Callback, der das beobachtete Element fortlaufend in der Größe verändert, löst den Fehler in jedem Frame aus.
  • try/catch bekommt den Fehler nie zu sehen, window.onerror, Error-Tracking-SDKs und der uncaught:exception-Handler von Cypress dagegen schon.
  • Filtern Sie den Fehler in Ihrem Tracker statt ihn global über window.onerror zu unterdrücken, und behalten Sie im Blick, wie häufig er auftritt.

Was bedeutet „ResizeObserver Loop Completed With Undelivered Notifications“?

Die Meldung „ResizeObserver loop completed with undelivered notifications“ bedeutet, dass der Browser Resize-Benachrichtigungen hatte, die er nicht innerhalb eines Frames zustellen konnte, und sie deshalb zurückgestellt hat. Falls Sie Ihr Wissen zur API selbst auffrischen möchten, lesen Sie ResizeObserver vs window resize.

Der Browser verarbeitet Resize-Beobachtungen nach dem Layout und vor dem Paint. Ändert ein Callback das Layout, berechnet der Browser es neu und führt die Callbacks erneut aus. In jedem Durchlauf verarbeitet er nur Elemente, die im DOM tiefer liegen als das am wenigsten tief verschachtelte Element des vorherigen Durchlaufs. Alles, was diese Regel nicht erfüllt, wartet auf den nächsten Frame, und der Browser meldet den Fehler. Der Abschnitt „Observation Errors“ bei MDN beschreibt diesen Ablauf, die Resize-Observer-Spezifikation definiert die Tiefenregel.

Sie sehen eine von zwei Meldungen:

  • ResizeObserver loop completed with undelivered notifications. (aktueller Wortlaut, mit abschließendem Punkt)
  • ResizeObserver loop limit exceeded (Wortlaut älterer Browserversionen)

Ein minimales Reproduktionsbeispiel

Diese Datei reproduziert beide Verhaltensweisen. Der Callback ändert die Breite des Elements, das er beobachtet.

<div id="box" style="width:100px;height:40px;background:tomato"></div>
<script>
  const box = document.getElementById('box');
  const LOOP = false; // set to true for the endless case

  window.addEventListener('error', (e) => console.log('window error:', e.message));

  const ro = new ResizeObserver(([entry]) => {
    const width = entry.contentBoxSize[0].inlineSize;
    entry.target.style.width = LOOP ? `${width + 10}px` : '200px';
  });
  ro.observe(box);
</script>

Mit LOOP = false setzt der erste Callback die Box auf 200px. Diese Größenänderung betrifft dasselbe Element, liegt also nicht tiefer als das gerade verarbeitete Element. Der Browser stellt sie zurück und protokolliert den Fehler einmal. Im nächsten Frame schreibt der Callback erneut 200px, nichts ändert sich, und der Observer feuert nicht mehr.

Mit LOOP = true wird die Box in jedem Frame breiter, und der Fehler wird in jedem Frame protokolliert.

Warum fängt try/catch den ResizeObserver-Fehler nicht ab?

try/catch fängt den ResizeObserver-Loop-Fehler nie ab, weil Ihr Callback nichts wirft. Er wird regulär beendet. Der Browser löst anschließend ein error-Event auf window aus, außerhalb jedes Call-Stacks, den Sie kontrollieren.

try {
  ro.observe(box);
} catch (err) {
  console.log('never reached'); // the error is dispatched on window later
}

window.onerror und error-Listener erfassen Error-Events auf Window-Ebene, und Error-Tracking-SDKs klinken sich in dieselben Events ein – deshalb zeichnen Tracker den Fehler auf. Das Event enthält kein Error-Objekt mit Stack-Trace, weshalb Tracker in der Regel keinen brauchbaren Stack anzeigen.

Wann ist der ResizeObserver-Fehler ein echtes Problem?

Relevant wird der ResizeObserver-Loop-Fehler, wenn sich das Layout nicht stabilisiert. MDN weist darauf hin, dass der Schutzmechanismus zwar ein Einfrieren des Browsers verhindert, nicht aber die Schleife selbst. Selbst eine Schleife, die sich stabilisiert, kann kurzzeitig ein Zwischenlayout anzeigen, während die Änderungen auf mehrere Frames verteilt werden.

Was Sie sehenWas es bedeutetWas zu tun ist
Einige wenige Events nach dem Laden oder einer Layoutänderung, die Seite wirkt stabilDie Schleife hat sich innerhalb von ein bis zwei Frames stabilisiertFiltern
Häufungen, die an eine Komponente gebunden sind, mit sichtbarem SprungEine Komponente ändert die Größe der Box, die sie beobachtetBeheben oder upstream melden
Ein kontinuierlicher Strom, ein wachsendes Element oder steigende CPU-LastEine Schleife, die sich nie stabilisiertSofort beheben

Wie behebt man eine ResizeObserver-Schleife?

Sie beheben eine ResizeObserver-Schleife, indem Sie verhindern, dass der Callback das beobachtete Element in der Größe verändert, oder indem Sie diesen Schreibzugriff aus dem aktuellen Frame herauslösen. Gehen Sie die folgenden Optionen der Reihe nach durch.

  1. Ändern Sie die Größe des beobachteten Elements nicht in dessen eigenem Callback. Schreiben Sie in ein Element, dessen Größe nicht auf die Beobachtung zurückwirkt. Für rein visuelle Responsivität kommen CSS Container Queries ganz ohne JavaScript aus.
  2. Mit requestAnimationFrame verzögern. Verschieben Sie den Schreibzugriff in requestAnimationFrame, wird er im nächsten Frame ausgeführt, und der Fehler verschwindet. MDN empfiehlt dies, wenn Sie eine fortlaufende Größenänderung beabsichtigen. Im LOOP-Fall wächst die Box dennoch endlos weiter. Nur der Fehler verschwindet.
  3. Selbst gesetzte Größen überspringen. Für unbeabsichtigte Schleifen schlägt MDN vor, die erwartete Größe jedes Elements in einer WeakMap festzuhalten:
const expected = new WeakMap();

const ro = new ResizeObserver((entries) => {
  for (const entry of entries) {
    const width = entry.contentBoxSize[0].inlineSize;
    if (expected.get(entry.target) === width) continue; // our own change

    requestAnimationFrame(() => {
      const next = width + 10;
      entry.target.style.width = `${next}px`;
      expected.set(entry.target, next);
    });
  }
});

Da der Zustand pro Element gespeichert wird, lassen sich problemlos mehrere Elemente beobachten. Sieht der Callback die Breite, die er selbst gesetzt hat, überspringt er den Eintrag, und die Schleife endet.

Auch UI-Bibliotheken lösen den Fehler aus

Der übliche Auslöser ist ein ResizeObserver-Callback, der die Größe des beobachteten Elements ändert. Dieser Callback steckt häufig in einer Komponentenbibliothek statt in Ihrem eigenen Code. Typische Quellen sind Komponenten, die ihren Container vermessen und dann sich selbst oder ein Kindelement in der Größe anpassen, etwa automatisch skalierende Textfelder, virtualisierte Tabellen, Charts und Popover. Tritt der Fehler auf, wenn eine bestimmte Komponente gemountet oder geöffnet wird, aktualisieren Sie die Bibliothek oder melden Sie upstream ein Reproduktionsbeispiel.

Wie filtert man den Fehler sicher?

Erfassen Sie beide Meldungen mit einem einzigen Muster und wenden Sie es in Ihrem Tracker und Ihrem Test-Runner an, nicht in einem globalen Handler.

const RO_LOOP_ERROR =
  /ResizeObserver loop (completed with undelivered notifications|limit exceeded)/;

// cypress/support/e2e.js
Cypress.on('uncaught:exception', (err) => {
  if (RO_LOOP_ERROR.test(err.message)) return false;
});

Cypress löst uncaught:exception immer dann aus, wenn Ihre App einen Fehler wirft, den nichts abfängt. Ein Handler, der false zurückgibt, weist Cypress an, den Test nicht fehlschlagen zu lassen. Dokumentiert ist dies im Cypress Catalog of Events. In Sentry fügen Sie ignoreErrors: [RO_LOOP_ERROR] hinzu. Laut der Sentry-Optionsreferenz werden String-Einträge als Teilstring abgeglichen und Regex-Einträge unverändert geprüft. Aktuelle Sentry-JavaScript-SDKs ignorieren den modernen Wortlaut bereits standardmäßig, den alten jedoch nicht. Ein eigener Eintrag ist daher nur für den alten String oder für ältere SDKs nötig.

Unterdrücken Sie den Fehler nicht in window.onerror. Eine pauschale Unterdrückung verbirgt auch den Fall der Endlosschleife, und Sie verlieren das Signal der Auftretenshäufigkeit. Das Filtern auf Tracker-Ebene, wie in diesen Best Practices für das Error-Logging beschrieben, hält diese Entscheidung sichtbar und leicht umkehrbar. Behalten Sie anschließend die Anzahl im Auge. Eine gelegentliche Häufung nach einer Layoutänderung ist Rauschen. Ein stetiger Strom aus einer einzigen Session deutet auf eine Schleife hin, die sich nie stabilisiert. Ein Session-Replay zeigt, ob der Nutzer zum Zeitpunkt des Fehlers einen Layoutsprung gesehen hat, und verrät Ihnen so, ob es sich um Rauschen handelt. Dieselbe Triage gilt auch für andere störende Produktionsfehler wie „Failed to Fetch Dynamically Imported Module“.

Fazit

In den meisten Fällen bedeutet ein „ResizeObserver loop completed“-Fehler, dass der Browser einige Benachrichtigungen zurückgestellt hat und sich das Layout einen Frame später stabilisiert hat. Prüfen Sie, ob sich das Layout stabilisiert und ob etwas sichtbar springt. Fallen beide Antworten positiv aus, filtern Sie beide Meldungen in Ihrem Tracker. Andernfalls suchen Sie den Callback, der das beobachtete Element in der Größe verändert, und beheben Sie das Problem mit einer Verzögerung oder einem Guard für die erwartete Größe.

FAQs

Sind setTimeout oder Debounce eine bessere Lösung als requestAnimationFrame?

Nein. Beide verschieben den Schreibzugriff aus dem aktuellen Frame, sodass der Fehler verschwindet, aber keines von beiden stoppt eine Schleife, die das Element immer weiter wachsen lässt. Ein setTimeout-Callback kann ausgeführt werden, nachdem der Browser bereits gezeichnet hat, sodass Nutzer für einen Frame das alte Layout sehen können, bevor die Änderung greift. requestAnimationFrame wird vor dem nächsten Paint ausgeführt und hält das Update so im Takt mit dem Rendering.

Warum tritt der Fehler direkt nach dem Mounten einer Komponente auf?

ResizeObserver sendet eine erste Benachrichtigung, wenn observe() für ein Element aufgerufen wird, das gerendert ist und eine Größe ungleich null hat. Schreibt der Callback bei dieser ersten Benachrichtigung eine neue Größe in das Element, stellt der Browser die Folgebenachrichtigung zurück und meldet den Fehler sofort. Komponenten, die sich beim Mounten selbst vermessen und in der Größe anpassen, lösen ihn auf diese Weise aus, selbst wenn auf der Seite sonst nichts in der Größe verändert wird.

Können CSS Container Queries einen ResizeObserver-Callback ersetzen?

Ja, sofern der Callback lediglich Styles auf Basis der Containerbreite ändert. Container Size Queries wenden Size Containment an, sodass der Container seine Größe nicht von seinem Inhalt ableitet und das Styling nicht auf die Messung zurückwirken kann. Size Queries funktionieren ab Chrome 105, Safari 16 und Firefox 110. Verwenden Sie ResizeObserver weiterhin für Aufgaben, die CSS nicht leisten kann, etwa das Neuberechnen von Chart-Abmessungen oder der Zeilenanzahl in virtualisierten Listen.

Wie finde ich den auslösenden Code, wenn der Stack-Trace leer ist?

Ersetzen Sie in der Entwicklung window.ResizeObserver vorübergehend durch eine Unterklasse, die jedes beobachtete Element protokolliert und den Callback umschließt. Reproduzieren Sie dann den Fehler und prüfen Sie, welches Element unmittelbar vor dem Window-Error-Event in der Größe verändert wurde. Steckt der Callback in einer Drittanbieter-Bibliothek, grenzen Sie die Quelle ein, indem Sie Komponenten nacheinander entfernen, bis der Fehler verschwindet.

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — self-hosted, with full data ownership.

Star on GitHub

We use cookies to improve your experience. By using our site, you accept cookies.