Jak rozwiązywać problemy z Angularem „Osiągnięto iteracje 10 $ straw ()”

91

Osiągnięto 10 $ straw () iteracji. Przerwanie!

Jest dużo tekstu pomocniczego w sensie „Obserwatorzy uruchomieni w ostatnich 5 iteracjach:” itp., Ale wiele z tego tekstu to kod JavaScript z różnych funkcji. Czy istnieją praktyczne zasady dotyczące diagnozowania tego problemu? Czy jest to problem, który ZAWSZE można złagodzić, czy też istnieją aplikacje na tyle złożone, że należy traktować ten problem tylko jako ostrzeżenie?

blaster
źródło

Odpowiedzi:

80

jak powiedział Ven, albo zwracasz różne (nie identyczne) obiekty w każdym $digestcyklu, albo zbyt wiele razy zmieniasz dane.

Najszybszym sposobem ustalenia, która część aplikacji powoduje takie zachowanie, jest:

  1. usuń cały podejrzany kod HTML - po prostu usuń cały kod HTML z szablonu i sprawdź, czy nie ma żadnych ostrzeżeń
  2. jeśli nie ma ostrzeżeń - dodaj małe fragmenty usuniętego kodu HTML i sprawdź, czy problem wrócił
  3. powtarzaj krok 2, aż pojawi się ostrzeżenie - dowiesz się, która część Twojego html jest odpowiedzialna za problem
  4. zbadaj dalej - część z kroku 3 jest odpowiedzialna za mutację obiektów na $scopelub zwraca nieidentyczne obiekty w każdym $digestcyklu.
  5. jeśli nadal masz $digestostrzeżenia o iteracji po kroku 1, prawdopodobnie robisz coś bardzo podejrzanego. Powtórz te same kroki dla szablonu / zakresu / kontrolera nadrzędnego

Chcesz się również upewnić, że nie zmieniasz danych wejściowych własnych filtrów

Pamiętaj, że w JavaScript istnieją określone typy obiektów, które nie zachowują się tak, jak można by się normalnie spodziewać:

new Boolean(true) === new Boolean(true) // false
new Date(0) == new Date(0) // false
new String('a') == new String('a') // false
new Number(1) == new Number(1) // false
[] == [] // false
new Array == new Array // false
({})==({}) // false
g00fy
źródło
5
Dziękuję Ci! To jest pomocna heurystyka. Myślę też, że nowa funkcja Angulara „track by” dla ngRepeat też mi pomoże. Robię trochę map () i groupBy () używając Underscore w usłudze, więc zdecydowanie zwraca "różne" obiekty za każdym razem (nawet jeśli logicznie reprezentują te same rzeczy - "track by Id" pomoże Angularowi ich nie zobaczyć jako „zmiana”, jeśli tak naprawdę nie jest).
blaster
2
Jeśli to robisz, map()a groupBy()potem upewnij się, że twoje ES$watch wykonują brudne sprawdzanie, objectEquality $watch('myFunctionDoingGropby',callback,true) patrz docs.angularjs.org/api/ng.$rootScope.Scope#$watch
g00fy
Zgodnie z powyższym komentarzem, „śledzenie” rozwiązało mój problem (zobacz dokumenty tutaj: docs.angularjs.org/api/ng/directive/ngRepeat ). Byłbym wdzięczny za komentarz wyjaśniający wyjaśniający, dlaczego to działa ...
Soferio
@ g00fy: To był dobry trop i udało mi się zastąpić moją funkcję pobierania listy zmienną na mojej, $scopektórej przypisywana jest _.maplista -ed - ale w ogólnym przypadku, jak wpłynąłbyś na wspomniane brudne sprawdzanie przez równość obiektów, jeśli to nie jest ręcznie utworzony, $watchktóry się potyka, ale ngRepeat?
LUB Mapper
73

Zwykle dzieje się tak, gdy za każdym razem zwracasz inny obiekt.

Na przykład, jeśli używasz tego w ng-repeat:

$scope.getObj = function () {
  return [{a: 1}, {b: 2}];
};

Otrzymasz ten komunikat o błędzie, ponieważ Angular stara się mieć „stabilność” i będzie wykonywał funkcję, dopóki nie zwróci tego samego wyniku 2 razy (w porównaniu z ===), co w naszym przypadku nigdy nie zwróci true, ponieważ funkcja zawsze zwraca wartość nowy obiekt.

console.log({} === {}); // false. Those are two different objects!

W takim przypadku możesz to naprawić, zapisując obiekt bezpośrednio w zasięgu, np

$scope.objData = [{a: 1}, {b: 2}];
$scope.getObj = function () {
  return $scope.objData;
};

W ten sposób zawsze zwracasz ten sam obiekt!

console.log($scope.objData === $scope.objData); // true (a bit obvious...)

(Nigdy nie powinieneś się z tym spotkać, nawet w przypadku złożonych aplikacji).

Aktualizacja: Angular dodał bardziej szczegółowe wyjaśnienie na swojej stronie internetowej .

Ven
źródło
Jak temu zapobiec? Mam teraz podobną sytuację.
Conqueror,
2
Upewnij się, że nie tworzysz różnych obiektów przy każdym wywołaniu;).
Ven
Jak mogę to zapewnić? Robię coś w rodzaju item w func (obj), ale func wydaje się być wywoływana wiele razy, a nie raz, jak mam nadzieję. Próbowałem użyć ng-init, aby wywołać func, a następnie dołączyć go do modelu na lunecie, ale to też nie zadziałało.
Conqueror,
1
To jest przykład, który widzisz wszędzie w tym problemie. Byłoby wspaniale, gdyby Angular mógł wskazać na obraźliwą funkcję, która ma takie zachowanie ...
Jorn
1
@BenWheeler Z pewnością poprawił się od 2013 roku, tak: P.
Ven
11

Chciałem tylko wrzucić to rozwiązanie tutaj, mam nadzieję, że pomoże innym. Otrzymałem ten problem z iteracją, ponieważ iterowałem po wygenerowanej właściwości, która tworzyła nowy obiekt za każdym razem, gdy był wywoływany.

Naprawiłem to, zapisując w pamięci podręcznej wygenerowany obiekt przy pierwszym żądaniu, a następnie zawsze zwracając pamięć podręczną, jeśli istniała. Dodano również metodę dirty (), która w razie potrzeby niszczyłaby buforowane wyniki.

Miałem coś takiego:

function MyObj() {
    var myObj = this;
    Object.defineProperty(myObj, "computedProperty" {
        get: function () {
            var retObj = {};

            return retObj;
        }
    });
}

A oto z wdrożonym rozwiązaniem:

function MyObj() {
    var myObj = this,
        _cached;
    Object.defineProperty(myObj, "computedProperty" {
        get: function () {
            if ( !_cached ) {
                _cached = {};
            }

            return _cached;
        }
    });

    myObj.dirty = function () {
        _cached = null;
    }
}
Asmor
źródło
O mój Boże, chciałbym dać Ci 10 głosów za. Właśnie rozwiązałeś problem, w wyniku którego przez prawie 3 godziny waliłem głową w ścianę. Kocham Cię!
GMA,
7

Istnieje również możliwość, że w ogóle nie będzie to nieskończona pętla. 10 iteracji nie jest wystarczająco dużą liczbą, aby stwierdzić to z jakąkolwiek pewnością. Dlatego przed wyruszeniem w pogoń za dziką gęsią warto najpierw wykluczyć tę możliwość.

Najłatwiejszą metodą jest zwiększenie maksymalnej liczby pętli skrótu do znacznie większej liczby, co można zrobić w module.configmetodzie, używając $rootScopeProvider.digestTtl(limit)metody. Jeśli infdigbłąd już się nie pojawia, masz po prostu wystarczająco złożoną logikę aktualizacji.

Jeśli budować danych lub widoków zależnych od rekurencyjnych zegarków może chcesz szukać rozwiązań iteracyjnych (czyli nie opierając się na nowych strawić pętli zostać uruchomiona) używając while, forlub Array.forEach. Czasami struktura jest po prostu silnie zagnieżdżona, a nawet nie rekursywna, prawdopodobnie w takich przypadkach nie ma wiele do zrobienia poza podniesieniem limitu.

Inną metodą debugowania błędu jest sprawdzenie danych skrótu. Jeśli dobrze wydrukujesz JSON, otrzymasz tablicę tablic. Każdy wpis najwyższego poziomu reprezentuje iterację, każda iteracja składa się z listy obserwowanych wpisów.

Jeśli na przykład masz właściwość, która została zmodyfikowana w $watchsamym sobie, łatwo zauważyć, że wartość zmienia się w nieskończoność:

$scope.vm.value1 = true;
$scope.$watch("vm.value1", function(newValue)
{
    $scope.vm.value1 = !newValue;
});
[
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":false,
         "oldVal":true
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":false,
         "oldVal":true
      }
   ],
   [
      {
         "msg":"vm.value1",
         "newVal":true,
         "oldVal":false
      }
   ]
]

Oczywiście w większych projektach może to nie być takie proste, zwłaszcza że msgpole często ma wartość, "fn: regularInterceptedExpression"jeśli zegarek jest {{ }}interpolowany.

Poza tym wspomniane już metody, takie jak wycinanie kodu HTML w celu znalezienia źródła problemu, są oczywiście pomocne.

HB
źródło
6

Miałem ten sam problem - za każdym razem tworzyłem nową datę. Więc dla każdego, kto ma do czynienia z datami, przekonwertowałem wszystkie połączenia w ten sposób:

var date = new Date(); // typeof returns object

do:

var date = new Date().getTime(); // typeof returns number

Zainicjowanie liczby zamiast obiektu daty rozwiązało to za mnie.

Arman Bimatov
źródło
2

Prosty sposób: użyj pliku angular.js, a nie pliku min. otwórz go i znajdź linię:

if ((dirty || asyncQueue.length) && !(ttl--)) {

dodaj wiersz poniżej:

console.log("aaaa",watch)

a następnie odśwież stronę, w konsoli narzędzi programistycznych znajdziesz kod błędu.

askie
źródło
0

Chciałbym również wspomnieć, że otrzymałem ten komunikat o błędzie, gdy miałem literówkę w templateUrl niestandardowej dyrektywy, którą miałem w moim projekcie. Z powodu literówki nie można załadować szablonu.

/* @ngInject */
function topNav() {
    var directive = {
        bindToController: true,
        controller: TopNavController,
        controllerAs: 'vm',
        restrict: 'EA',
        scope: {
            'navline': '=',
            'sign': '='
        },
        templateUrl: 'app/shared/layout/top-navTHIS-IS-A-TYPO.html'
    };

Spójrz na kartę sieciową narzędzi programistycznych swojej przeglądarki internetowej i sprawdź, czy któryś zasób ma błąd 404.

Łatwo przeoczyć, ponieważ komunikat o błędzie jest bardzo tajemniczy i pozornie niezwiązany z rzeczywistym problemem.

Casey Plummer
źródło
0

Miałem ten problem w moim projekcie, ponieważ .otherwise () nie zawierało mojej definicji trasy i trafiałem na złą trasę.

Sandeep M
źródło
0

Miałem ten problem, ponieważ to robiłem

var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
               return ve.rawMaterial.id = rawMaterial.id;
});

Zamiast tego: (uwaga = vs ===), mój test jednostkowy zaczął się psuć i znalazłem swoją głupotę

var variableExpense = this.lodash.find(product.variableExpenseList, (ve) => {
               return ve.rawMaterial.id === rawMaterial.id;
});
Maccurt
źródło
0

Natknąłem się na ten problem, w którym potrzebowałem dynamicznej podpowiedzi ... spowodowało to, że angular przeliczył ją za każdym razem jako nową wartość (mimo że była taka sama). Utworzyłem funkcję do buforowania obliczonej wartości w następujący sposób:

$ctrl.myObj = {
    Title: 'my title',
    A: 'first part of dynamic toolip',
    B: 'second part of dynamic tooltip',
    C: 'some other value',
    getTooltip: function () {
        // cache the tooltip
        var obj = this;
        var tooltip = '<strong>A: </strong>' + obj.A + '<br><strong>B: </strong>' + obj.B;
        var $tooltip = {
            raw: tooltip,
            trusted: $sce.trustAsHtml(tooltip)
        };
        if (!obj.$tooltip) obj.$tooltip = $tooltip;
        else if (obj.$tooltip.raw !== tooltip) obj.$tooltip = $tooltip;
        return obj.$tooltip;
    }
};

Następnie w html uzyskałem do niego dostęp w ten sposób:

<input type="text" ng-model="$ctrl.myObj.C" uib-tooltip-html="$ctrl.myObj.getTooltip().trusted">
Scrappy Doo
źródło
0

tak podszedłem do tego i znalazłem rozwiązanie: sprawdziłem tekst, pokazał:

Error: [$rootScope:infdig] 10 $digest() iterations reached. Aborting!

Obserwatorzy uruchomieni w ostatnich 5 iteracjach: [[{"msg": "statement === statment && functionCall ()", "newVal": [{"id": 7287, "odwołanie ...

więc jeśli widzisz plik

msg

to jest instrukcja powodująca błąd. Sprawdziłem funkcję wywołaną w tej wiadomości, zwróciłem (false) z nich wszystkich tylko po to, aby określić, który z nich ma problem. jeden z nich wywoływał funkcję, która ciągle zmienia wartość powrotu, co jest problemem.

Ahmed M. Matar
źródło
-3

Choć brzmi to szalenie, naprawiłem ten błąd, po prostu ponownie uruchamiając przeglądarkę, gdy nagle się pojawiła.

Jednym z rozwiązań jest po prostu wyczyszczenie pamięci podręcznej przeglądarki lub próba ponownego uruchomienia przeglądarki.

cst1992
źródło