Naruszenie Długotrwałe zadanie JavaScript zajęło xx ms


330

Ostatnio dostałem takie ostrzeżenie i po raz pierwszy je otrzymałem:

[Violation] Long running JavaScript task took 234ms
[Violation] Forced reflow while executing JavaScript took 45ms

Pracuję nad projektem grupowym i nie mam pojęcia, skąd on pochodzi. To się nigdy wcześniej nie zdarzyło. Nagle pojawiło się, gdy ktoś inny zaangażował się w projekt. Jak znaleźć plik / funkcję, która powoduje to ostrzeżenie? Szukałem odpowiedzi, ale głównie o tym, jak ją rozwiązać. Nie mogę tego rozwiązać, jeśli nawet nie mogę znaleźć źródła problemu.

W takim przypadku ostrzeżenie pojawia się tylko w Chrome. Próbowałem użyć Edge, ale nie otrzymałem podobnych ostrzeżeń i nie przetestowałem go jeszcze w Firefoksie.

Błąd pojawia się nawet w jquery.min.js:

[Violation] Handler took 231ms of runtime (50ms allowed)            jquery.min.js:2

Gdzie widzisz to ostrzeżenie? Nie mówisz, w jakim środowisku pracujesz. Zakładając jakąś przeglądarkę, ale która itd.?
Sami Kuhmonen,

3
@SamiKuhmonen przepraszam za to, zaktualizowałem swoje pytanie. użyłem Chrome. nie znalazłem podobnego błędu na Edge.
procatmer

8
Chciałem tylko dodać, że ten komunikat ostrzegawczy, wprowadzony pod koniec 2016 r., Może również pojawić się z powodu rozszerzeń zainstalowanych w Chrome. Można to łatwo sprawdzić, testując w trybie prywatnym.
Fer

1
Kliknięcie linku po prawej stronie, wskazującego skrypt, w którym dochodzi do naruszenia, przeniesie Cię do miejsca w kodzie, w którym ma miejsce.
bluehipy

Używam Ionic 4 (Angular 8), mój kod działał dobrze, nagle zaczęło się pojawiać takie naruszenie - na mojej liście nie ma już żadnych danych?
Kapil Raghuwanshi

Odpowiedzi:


278

Aktualizacja : Chrome 58+ domyślnie ukrył te i inne komunikaty debugowania. Aby je wyświetlić, kliknij strzałkę obok „Informacje” i wybierz „Pełne”.

Chrome 57 domyślnie włączył opcję „ukryj naruszenia”. Aby je ponownie włączyć, musisz włączyć filtry i odznaczyć pole „ukryj naruszenia”.

nagle pojawia się, gdy ktoś inny zaangażowany w projekt

Myślę, że bardziej prawdopodobne jest, że zaktualizowałeś przeglądarkę Chrome 56. To ostrzeżenie jest cudowną nową funkcją, moim zdaniem, wyłącz ją tylko, jeśli jesteś zdesperowany, a Twój asesor odbierze ci oceny. Problemy leżą u podstaw innych przeglądarek, ale przeglądarki po prostu nie informują o problemie. Bilet Chromium jest tutaj ale tak naprawdę nie ma żadnej interesującej dyskusji na jego temat.

Te komunikaty są ostrzeżeniami zamiast błędów, ponieważ tak naprawdę nie będą powodować poważnych problemów. Może to powodować upuszczanie ramek lub w inny sposób powodować mniej płynne działanie.

Warto je jednak zbadać i naprawić, aby poprawić jakość aplikacji. Można to zrobić, zwracając uwagę na okoliczności, w których pojawiają się komunikaty, i przeprowadzając testy wydajności, aby zawęzić miejsce występowania problemu. Najprostszym sposobem rozpoczęcia testowania wydajności jest wstawienie takiego kodu:

function someMethodIThinkMightBeSlow() {
    const startTime = performance.now();

    // Do the normal stuff for this function

    const duration = performance.now() - startTime;
    console.log(`someMethodIThinkMightBeSlow took ${duration}ms`);
}

Jeśli chcesz być bardziej zaawansowany, możesz także użyć profilera Chrome lub skorzystać z biblioteki testów porównawczych, takiej jak ta .

Gdy znajdziesz trochę kodu, który zajmuje dużo czasu (50 ms to próg Chrome), masz kilka opcji:

  1. Wytnij część / całość tego zadania, które może być niepotrzebne
  2. Dowiedz się, jak szybciej wykonać to samo zadanie
  3. Podziel kod na wiele asynchronicznych kroków

(1) i (2) mogą być trudne lub niemożliwe, ale czasem są naprawdę łatwe i powinny być twoimi pierwszymi próbami. W razie potrzeby zawsze powinno być to możliwe (3). Aby to zrobić, użyjesz czegoś takiego:

setTimeout(functionToRunVerySoonButNotNow);

lub

// This one is not available natively in IE, but there are polyfills available.
Promise.resolve().then(functionToRunVerySoonButNotNow);

Możesz przeczytać więcej o asynchronicznej naturze JavaScript tutaj .


16
Tylko sugestia, zamiast używać performance.now(), możesz użyć console.time( developer.mozilla.org/en-US/docs/Web/API/Console/time ) console.time('UniquetLabelName') ....code here.... console.timeEnd('UniqueLabelName')
denislexic

@denislexic Chyba tak. Nie jestem jednak pewien, jaką wartość to naprawdę dodaje. Twierdziłbym, że zdobycie wiedzy na temat operacji polegającej na uzyskaniu aktualnego czasu i czerpaniu z niego jest cenniejsze.
voltrevo

34
Świetna odpowiedź, voltrevo! Moje pytanie brzmi: jeśli taki kod jest naruszeniem, co dokładnie to narusza? Musi istnieć jakiś standard stosowany przez Google, ale czy ten standard jest publicznie udokumentowany?
Bungler

1
@Bungler Dunno, chciałbym wiedzieć, czy istnieje jakaś wskazówka, do której się odnosi.
voltrevo

3
@Bungler Mogę tylko zgadywać, że mówi, że animowany kod narusza dostarczanie co najmniej 60 klatek na sekundę, a tym samym daje złe wrażenia użytkownika. .
user895400

90

To tylko ostrzeżenia, jak wszyscy wspominali. Jeśli jednak chcesz je rozwiązać (co powinieneś), musisz najpierw ustalić, co powoduje ostrzeżenie. Nie ma jednego powodu, dla którego można uzyskać ostrzeżenie o przepływie wymuszonym. Ktoś utworzył listę niektórych możliwych opcji. Możesz śledzić dyskusję, aby uzyskać więcej informacji.
Oto sedno możliwych przyczyn:

Co wymusza układ / przepływ

Wszystkie poniższe właściwości lub metody, gdy są wymagane / wywoływane w JavaScript, powodują, że przeglądarka synchronicznie oblicza styl i układ *. Nazywa się to również reflow lub thrashingiem układu i jest częstym wąskim gardłem wydajności.

Element

Dane pola
  • elem.offsetLeft, elem.offsetTop, elem.offsetWidth, elem.offsetHeight,elem.offsetParent
  • elem.clientLeft, elem.clientTop, elem.clientWidth,elem.clientHeight
  • elem.getClientRects(), elem.getBoundingClientRect()
Przewiń rzeczy
  • elem.scrollBy(), elem.scrollTo()
  • elem.scrollIntoView(), elem.scrollIntoViewIfNeeded()
  • elem.scrollWidth, elem.scrollHeight
  • elem.scrollLeft, elem.scrollToptakże ich ustawianie
Skupiać
  • elem.focus() może wywołać podwójny wymuszony układ ( źródło )
Również…
  • elem.computedRole, elem.computedName
  • elem.innerText( źródło )

getComputedStyle

window.getComputedStyle()zwykle wymusza ponowne przeliczanie stylu ( źródło )

window.getComputedStyle() wymusi również układ, jeśli spełniony jest jeden z poniższych warunków:

  1. Element znajduje się w cieniu drzewa
  2. Istnieją zapytania o media (związane z oknem ekranu). W szczególności, jeden z poniższych: ( źródła ) * min-width, min-height, max-width, max-height, width, height * aspect-ratio, min-aspect-ratio,max-aspect-ratio
    • device-pixel-ratio, resolution,orientation
  3. Żądana właściwość jest jedną z następujących czynności: ( źródło )
    • height, width * top, right, bottom, left * margin[ -top, -right, -bottom, -leftAlbo skróconą ] Tylko wtedy, gdy brzeg zostanie rozwiązany. * padding[ -top, -right, -bottom, -leftAlbo skróconą ] Tylko wtedy, gdy wyściółka jest stała. * transform, transform-origin, perspective-origin * translate, rotate, scale * webkit-filter, backdrop-filter * motion-path, motion-offset, motion-rotation * x, y, rx,ry

okno

  • window.scrollX, window.scrollY
  • window.innerHeight, window.innerWidth
  • window.getMatchedCSSRules() tylko wymusza styl

Formularze

  • inputElem.focus()
  • inputElem.select(), textareaElem.select()( źródło )

Wydarzenia z myszką

  • mouseEvt.layerX, mouseEvt.layerY, mouseEvt.offsetX, mouseEvt.offsetY ( Źródło )

dokument

  • doc.scrollingElement tylko wymusza styl

Zasięg

  • range.getClientRects(), range.getBoundingClientRect()

SVG

zadowalający

  • Wiele rzeczy,… w tym kopiowanie obrazu do schowka ( źródło )

Sprawdź więcej tutaj .

Oto kod źródłowy Chromium z oryginalnego wydania i dyskusja na temat interfejsu API wydajności dla ostrzeżeń.


Edycja: Jest też artykuł o tym, jak zminimalizować zmiany układu w PageSpeed ​​Insight firmy Google . Wyjaśnia, co to jest przepełnienie przeglądarki:

Reflow to nazwa procesu przeglądarki internetowej służącego do ponownego obliczania pozycji i geometrii elementów w dokumencie, w celu ponownego renderowania części lub całości dokumentu. Ponieważ przepływ jest operacją blokującą użytkownika w przeglądarce, programiści powinni zrozumieć, jak poprawić czas przepływu, a także zrozumieć wpływ różnych właściwości dokumentu (głębia DOM, wydajność reguł CSS, różne typy zmian stylu) na przepływ czas. Czasami ponowne wlanie jednego elementu do dokumentu może wymagać ponownego wlania jego elementów nadrzędnych, a także wszelkich elementów, które następują po nim.

Ponadto wyjaśnia, jak to zminimalizować:

  1. Zmniejsz niepotrzebną głębokość DOM. Zmiany na jednym poziomie w drzewie DOM mogą powodować zmiany na każdym poziomie drzewa - aż do katalogu głównego i aż do potomków zmodyfikowanego węzła. Prowadzi to do dłuższego spędzania czasu na wykonywaniu przepływu.
  2. Zminimalizuj reguły CSS i usuń nieużywane reguły CSS.
  3. Jeśli wprowadzasz złożone zmiany renderowania, takie jak animacje, rób to płynnie. Aby to osiągnąć, użyj bezwzględnej pozycji lub ustalonej pozycji.
  4. Unikaj niepotrzebnych skomplikowanych selektorów CSS - w szczególności selektorów potomków - które wymagają większej mocy procesora, aby dopasować selektor.

1
Więcej informacji: kod źródłowy Chromium z oryginalnego wydania i dyskusja na temat wydajnego interfejsu API ostrzeżeń.
robocat

1
Zgodnie z powyższym, po prostu czytanie elementu.scrollTop uruchamia ponowne wycieki. Uderza mnie to jako zjawisko sprzeczne z intuicją. Rozumiem, dlaczego ustawienie elementu.scrollTop spowodowałoby ponowne wycieki, ale po prostu odczytanie jego wartości? Czy ktoś może wyjaśnić, dlaczego tak jest, jeśli w rzeczywistości tak jest?
David Edwards,

29

Kilka pomysłów:

  • Usuń połowę swojego kodu (być może poprzez komentowanie go).

    • Czy problem nadal występuje? Świetnie, zawęziłeś możliwości! Powtarzać.

    • Czy nie ma problemu? Ok, spójrz na połowę, którą skomentowałeś!

  • Czy używasz systemu kontroli wersji (np. Git)? Jeśli tak, git checkoutniektóre z ostatnich zatwierdzeń. Kiedy wprowadzono problem? Spójrz na zatwierdzenie, aby zobaczyć dokładnie, jaki kod zmienił się, gdy problem pojawił się po raz pierwszy.


Dziękuję za Twoją odpowiedź. usunąłem połowę, a nawet wykluczyłem mój główny plik .js z projektu. jakoś błąd nadal występował. dlatego tak mnie to frustruje. i tak, używam git. właśnie zdałem sobie sprawę z tego błędu dzisiaj. Od czasu, gdy stał się projektem grupowym, było wiele zobowiązań. może przeprowadzić głęboką kontrolę. jeszcze raz dziękuję za pomysły.
procatmer

@procatmer używa tej samej strategii do znalezienia git commit. Na przykład, gdybym miał 10 zatwierdzeń (A, B, C, D, E, F, G, H, I, J), gdzie A był najstarszy, git checkout Esprawdziłbym, czy problem już istnieje. Jeśli tak, będę nadal szukać problemu w pierwszej połowie zatwierdzeń. W przeciwnym razie szukam problemu w drugiej połowie.
therobinkim,

1
@procatmer Ponadto, jeśli pominiesz główny .jsplik, a problem będzie się powtarzał ... może to być biblioteka przywieziona przez <script src="...">tag! Może coś, o co nie warto się martwić (zwłaszcza, że ​​to tylko ostrzeżenie)?
therobinkim,

1
w końcu odkryłem, gdzie jest problem. wykorzystałem twój drugi pomysł do śledzenia zmian. i tak, problem pochodzi z zewnętrznego .jspliku. najwyraźniej ma to znaczenie. spowalnia moją stronę. w każdym razie jeszcze raz dziękuję za odpowiedzi i pomysły.
procatmer

2
Możesz użyć git bisect, aby zastosować wyszukiwanie binarne. Myślę, że to tylko w celu znalezienia błędu.
pietrovismara

12

Aby zidentyfikować źródło problemu, uruchom aplikację i zapisz ją na karcie Wydajność Chrome .

Tam możesz sprawdzić różne funkcje, których uruchomienie zajęło dużo czasu. W moim przypadku ten, który korelował z ostrzeżeniami w konsoli, pochodzi z pliku załadowanego przez rozszerzenie AdBlock, ale może to być coś innego w twoim przypadku.

Sprawdź te pliki i spróbuj ustalić, czy jest to kod jakiegoś rozszerzenia czy twój. (Jeśli to twoje, to znalazłeś źródło problemu).


Nie, nie mam AdBlocka i wciąż mam go w konsoli.
Nikola Stojaković

Spróbuj przeanalizować go za pomocą zakładki Wydajność i poszukaj źródła funkcji, które działają długo. Może to być cokolwiek, ale jest to potencjalny sposób na zidentyfikowanie źródła problemu.
Matt Leonowicz

6

Zajrzyj do konsoli Chrome na karcie Sieć i znajdź skrypty, których ładowanie trwa najdłużej.

W moim przypadku był zestaw skryptów Angular, które załączyłem, ale jeszcze nie użyłem w aplikacji:

<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-utils/0.1.1/angular-ui-utils.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular-animate.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.3.9/angular-aria.min.js"></script>

Były to jedyne pliki JavaScript, których ładowanie trwało dłużej niż czas określony przez błąd „Long Running Task”.

Wszystkie te pliki działają na moich innych stronach internetowych bez generowania błędów, ale otrzymywałem ten błąd „Long Running Task” w nowej aplikacji internetowej, która ledwo miała jakąkolwiek funkcjonalność. Błąd zatrzymał się natychmiast po usunięciu.

Domyślam się, że te Angularowe dodatki szukały rekurencyjnie w coraz głębszych sekcjach DOM dla swoich znaczników początkowych - nie znajdując żadnego, musiały przejść całą DOM przed wyjściem, co trwało dłużej niż się spodziewa Chrome - a więc ostrzeżenie.


6

Znalazłem źródło tej wiadomości w moim kodzie, który szukał i ukrywał lub pokazywał węzły (offline). To był mój kod:

search.addEventListener('keyup', function() {
    for (const node of nodes)
        if (node.innerText.toLowerCase().includes(this.value.toLowerCase()))
            node.classList.remove('hidden');
        else
            node.classList.add('hidden');
});

Karta wydajności (profiler) pokazuje zdarzenie trwające około 60 ms: Ponowne przeliczanie układu profilera wydajności chromu

Teraz:

search.addEventListener('keyup', function() {
    const nodesToHide = [];
    const nodesToShow = [];
    for (const node of nodes)
        if (node.innerText.toLowerCase().includes(this.value.toLowerCase()))
            nodesToShow.push(node);
        else
            nodesToHide.push(node);

    nodesToHide.forEach(node => node.classList.add('hidden'));
    nodesToShow.forEach(node => node.classList.remove('hidden'));
});

Karta wydajności (profiler) pokazuje teraz zdarzenie trwające około 1 ms: Chromowany profiler ciemny

I wydaje mi się, że wyszukiwanie działa teraz szybciej (229 węzłów).


3
Podsumowując, otrzymując naruszenie, mogłeś zoptymalizować swój kod i działa on teraz lepiej.
Użytkownik, który nie jest użytkownikiem

3

Znalazłem rozwiązanie w kodzie źródłowym Apache Cordova. Realizują w ten sposób:

var resolvedPromise = typeof Promise == 'undefined' ? null : Promise.resolve();
var nextTick = resolvedPromise ? function(fn) { resolvedPromise.then(fn); } : function(fn) { setTimeout(fn); };

Prosta implementacja, ale inteligentny sposób.

Na Androidzie 4.4 użyj Promise. W przypadku starszych przeglądarek użyjsetTimeout()


Stosowanie:

nextTick(function() {
  // your code
});

Po wstawieniu tego kodu sztuczki zniknęły wszystkie komunikaty ostrzegawcze.



2

Jeśli używasz Chrome Canary (lub Beta), po prostu zaznacz opcję „Ukryj naruszenia”.

Ukryj pole wyboru Naruszenia w konsoli Chrome 56


1

Jest to błąd naruszenia zasad przeglądarki Google Chrome, który pokazuje, kiedy Verbosepoziom rejestrowania jest włączony.

Przykład komunikatu o błędzie:

zrzut ekranu ostrzeżenia

Wyjaśnienie:

Reflow to nazwa procesu przeglądarki internetowej służącego do ponownego obliczania pozycji i geometrii elementów w dokumencie, w celu ponownego renderowania części lub całości dokumentu. Ponieważ przepływ jest operacją blokującą użytkownika w przeglądarce, przydatne jest dla programistów zrozumienie, jak poprawić czas ponownego przepływu, a także zrozumieć wpływ różnych właściwości dokumentu (głębia DOM, wydajność reguł CSS, różne typy zmian stylu) na przepływ czas. Czasami ponowne wlanie jednego elementu do dokumentu może wymagać ponownego wlania jego elementów nadrzędnych, a także wszelkich elementów, które następują po nim.

Artykuł oryginalny: Minimalizacja ponownego wczytywania przeglądarki przez Lindsey Simon, programistę UX, opublikowana na developers.google.com.

I to jest link, który Google Chrome podaje w Profilu wydajności, w profilach układu (regiony fioletowe), aby uzyskać więcej informacji na temat ostrzeżenia.


0

Dodając moje spostrzeżenia tutaj, ponieważ ten wątek był pytaniem „przejdź do” stosu przepływów na ten temat.

Mój problem dotyczył aplikacji Material-UI (wczesne etapy)

  • przyczyną było umieszczenie niestandardowego dostawcy motywu

kiedy wykonałem kilka obliczeń wymuszających renderowanie strony (jeden składnik, „wyświetl wyniki”, zależy od tego, co jest ustawione w innych, „sekcjach wejściowych”).

Wszystko było w porządku, dopóki nie zaktualizowałem „stanu”, który zmusza „komponent wyników” do ponownego wydania. Głównym problemem tutaj było to, że miałem motyw interfejsu użytkownika ( https://material-ui.com/customization/theming/#a-note-on-performance ) w tym samym rendererze (App.js / return ..) jako „składnik wyników”, SummaryAppBarPure

Rozwiązaniem było podniesienie ThemeProvider o jeden poziom wyżej (Index.js) i zawinięcie tutaj komponentu App, nie zmuszając w ten sposób ThemeProvider do ponownego obliczenia i narysowania / układu / przepływu.

przed

w App.js:

  return (
    <>
      <MyThemeProvider>
      <Container className={classes.appMaxWidth}>

        <SummaryAppBarPure
//...

w index.js

ReactDOM.render(
  <React.StrictMode>
      <App />
//...

po

w App.js:

return (
    <>
      {/* move theme to index. made reflow problem go away */}
      {/* <MyThemeProvider> */}
      <Container className={classes.appMaxWidth}>

        <SummaryAppBarPure
//...

w index.js

ReactDOM.render(
  <React.StrictMode>
    <MyThemeProvider>
      <App />
//...

-2

Wymuszone ponowne wlanie często zdarza się, gdy masz funkcję wywoływaną wiele razy przed końcem wykonania.

Na przykład problem może występować na smartfonie, ale nie w klasycznej przeglądarce.

Sugeruję użycie a setTimeoutdo rozwiązania problemu.

Nie jest to bardzo ważne, ale powtarzam, problem pojawia się, gdy wywołujesz funkcję kilka razy, a nie gdy funkcja trwa dłużej niż 50 ms. Myślę, że mylisz się w swoich odpowiedziach.

  1. Wyłącz połączenia 1 na 1 i ponownie załaduj kod, aby sprawdzić, czy nadal występuje błąd.
  2. Jeśli drugi skrypt powoduje błąd, użyj setTimeOutmetody opartej na czasie trwania naruszenia.

To nie jest rozwiązanie. Jest to sugestia, którą lepiej pozostawić jako komentarz do pierwotnego pytania.
Paul-Sebastian Manole

-6

To nie jest błąd, tylko zwykła wiadomość. Aby wykonać tę wiadomość, zmień
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">(przykład)
na
<!DOCTYPE html>(źródło Firefox się tego spodziewa)

. Wiadomość została pokazana w Google Chrome 74 i Opera 60. Po zmianie było jasne, 0 pełnych.
Podejście do rozwiązania


5
Tylko kilka porad: Twoja odpowiedź nie ma nic wspólnego z pytaniami. Napraw odpowiedź lub usuń ją. Pytanie brzmiało „dlaczego konsola przeglądarki Chrome wyświetla ostrzeżenie o naruszeniu zasad”. Odpowiedź jest taka, że ​​jest to funkcja w nowszych przeglądarkach Chrome, która ostrzega, jeśli strona internetowa powoduje nadmierne przepływy przeglądarki podczas wykonywania JS. Więcej informacji można znaleźć w tym zasobie od Google .
Paul-Sebastian Manole
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.