Błąd maksymalnego rozmiaru stosu wywołań

533

Używam pliku biblioteki JavaScript Direct Web Remoting (DWR) i pojawia się błąd tylko w przeglądarce Safari (komputer stacjonarny i iPad)

To mówi

Przekroczono maksymalny rozmiar stosu wywołań.

Co dokładnie oznacza ten błąd i czy przestaje on całkowicie przetwarzać?

Również wszelkie poprawki dla Safariprzeglądarki (w rzeczywistości iPad Safarimówi

JS: wykonanie przekroczyło limit czasu

zakładam, że to ten sam problem ze stosem wywołań)

testndtv
źródło
2
dostałem ten błąd, gdy próbuję wysłać zmienne (bez ich deklarowania), poprzez dataw ajax. Naprawiono błąd, deklarując zmienne.
phpfresher

Odpowiedzi:

621

Oznacza to, że gdzieś w kodzie wywołujesz funkcję, która z kolei wywołuje inną funkcję i tak dalej, dopóki nie osiągniesz limitu stosu wywołań.

Jest to prawie zawsze spowodowane funkcją rekurencyjną z przypadkiem podstawowym, który nie jest spełniony.

Wyświetlanie stosu

Rozważ ten kod ...

(function a() {
    a();
})();

Oto stos po kilku połączeniach ...

Inspektor sieci

Jak widać, stos wywołań rośnie, aż osiągnie limit: rozmiar stosu w przeglądarce lub wyczerpanie pamięci.

Aby to naprawić, upewnij się, że funkcja rekurencyjna ma przypadek podstawowy, który można spełnić ...

(function a(x) {
    // The following condition 
    // is the base case.
    if ( ! x) {
        return;
    }
    a(--x);
})(10);
alex
źródło
6
Dziękuję bardzo… Więc jest jakiś sposób / narzędzie, dzięki któremu mogę wyczerpać limit stosów… Znów, ponieważ jest to ogromny plik biblioteki i nie został stworzony przeze mnie, nie jestem w pełni świadomy, gdzie coś może pójść nie tak ...
testndtv
52
@Oliver Jeśli musisz uruchomić funkcję prawie 300 trylionów razy, prawdopodobnie robisz coś złego.
ceejayoz
3
@Oliver - możesz zajrzeć do programowania dynamicznego - domyślam się, że nie masz tylu unikalnych rozwiązań, powtarzasz kroki, więc może być konieczne zaprogramowanie go jako czasu wielomianowego, a nie kwadratowego. en.wikipedia.org/wiki/Dynamic_programming
newshorts
4
@Akhoy W zwykłej funkcji tak, ale pomyśl o funkcji rekurencyjnej. Wywołuje inny (sam) i pozostawia adres zwrotny tego pierwszego na stosie (w przeciwnym razie, gdzie komputer wiedziałby, gdzie iść?) To oczywiście wywołuje się ponownie, za każdym razem wypychając adres zwrotny na stos. Kiedy to ucieka z powodu podstawowego przypadku, który prawdopodobnie nie zostanie spełniony w następnym stuleciu, dostajesz przepełnienie stosu.
alex
5
Twój kod uruchamia ten sam nieefektywny segment 134217728 razy. Żart nie jest powolny. Powinieneś używać operatorów bitowych, aby móc rozwinąć 9 poziomów pętli w jeden poziom pętli.
Jack Giffin
82

Czasami możesz to uzyskać, jeśli przypadkowo zaimportujesz / osadzisz ten sam plik JavaScript dwa razy, co warto sprawdzić na karcie zasobów inspektora.

lucygenik
źródło
9
Ten problem zwykle występuje, gdy przypadkowo importujesz / osadzasz ten sam plik JS dwa razy. Dokładny proces, który powoduje pętlę rekurencyjną, nie jest czymś, co chciałbym spekulować, jednak zakładam, że jest to coś w rodzaju jazdy dwoma identycznymi samochodami z prędkością 100 km / h przez tę samą bramkę opłat drogowych.
lucygenik
3
Nie sądzę, że tak jest. Jeśli osadzone są dwa takie same funkcje lub zmienne, te ostatnie zastąpią poprzednie definicje.
ssudaraka
Tak ... Naprawdę nie wiem, dlaczego tak się dzieje ani jaki jest proces, pomyślałbym, że to też nadpisuje.
lucygenik
Trochę późno na imprezę, ale wyobrażam sobie, że to wtedy, gdy kod znajduje się poza jakąkolwiek funkcją, oba byłyby wykonywane w dwóch plikach JS i jeden prawdopodobnie nie nadpisałby się wzajemnie, ponieważ są one poza jakąkolwiek funkcją.
Cillian Collins
1
@lucygenik następnym razem, gdy będziesz chciał być bardziej ostrożny przy zatwierdzaniu edycji trolla w swoich postach. Ten post prawie został usunięty jako spam (sprawdź historię)
Jean-François Fabre
56

W moim przypadku wysyłałem elementy wejściowe zamiast ich wartości:

$.post( '',{ registerName: $('#registerName') } )

Zamiast:

$.post( '',{ registerName: $('#registerName').val() } )

To zamroziło moją kartę Chrome do tego stopnia, że ​​nawet nie pokazało mi okna dialogowego „Czekaj / zabij”, gdy strona przestała reagować ...

FKasa
źródło
3
Zastanawiam się, dlaczego nie ma wyjątku dla takiego błędu ... Dzięki!
Džuris,
Wielkie dzięki, uratowało mnie to
Eme Hado,
Chciałem dodać to jako możliwe rozwiązanie. Właśnie zrobiłem to dokładnie lol
Michael Buchok
30

Gdzieś w kodzie znajduje się pętla rekurencyjna (tj. Funkcja, która ostatecznie wywołuje się wielokrotnie, aż stos się zapełni).

Inne przeglądarki albo mają większe stosy (zamiast tego dostajesz limit czasu), albo z jakiegoś powodu połykają błąd (być może źle ustawiony try-catch).

Za pomocą debugera sprawdź stos wywołań, gdy wystąpi błąd.

Aaron Digulla
źródło
Bardzo dziękuję za odpowiedź. Na IE / FF kod wydaje się działać poprawnie. Tylko w przeglądarce Safari na komputerze stacjonarnym i iPadzie pojawia się błąd. W rzeczywistości plik JS nie jest moim własnym dziełem, ale jest plikiem biblioteki (z DWR engine.js) .. directwebremoting.org Także kiedy mówisz „debugger do sprawdzania stosu wywołań”, jak to zrobić za pomocą Safari Inspector?
testndtv
Nie mam doświadczenia z Safari Inspector. Spróbuj otworzyć debuger JavaScript i załaduj stronę. Debuger powinien zatrzymać się, gdy zostanie zgłoszony niezapisany błąd. Jeśli to nie zadziała, zapytaj administratora lub webmasterów (patrz dół strony)
Aaron Digulla
miał 2 funkcje o tej samej nazwie !! DOH!
jharris8567
16

Problem z wykrywaniem przepełnienia stosu polega na tym, że ślad stosu się odpręża i nie można zobaczyć, co się właściwie dzieje.

Uznałem, że niektóre z nowszych narzędzi do debugowania w Chrome są przydatne do tego.

Naciśnij Performance tab, upewnij się, że Javascript samplessą włączone, a dostaniesz coś takiego.

To oczywiste, gdzie jest przepełnienie! Jeśli klikniesz extendObject, będziesz w stanie zobaczyć dokładny numer wiersza w kodzie.

wprowadź opis zdjęcia tutaj

Możesz także zobaczyć czasy, które mogą być pomocne lub nie, lub czerwony śledź.

wprowadź opis zdjęcia tutaj


Kolejną przydatną sztuczką, jeśli nie możesz znaleźć problemu, jest umieszczenie wielu console.logstwierdzeń tam, gdzie Twoim zdaniem jest problem. Poprzedni krok powyżej może ci w tym pomóc.

W Chrome, jeśli wielokrotnie wyprowadzasz identyczne dane, wyświetli się w ten sposób, pokazując, gdzie problem jest wyraźniejszy. W tym przypadku stos uderzył w 7152 klatek, zanim ostatecznie się zawiesił:

wprowadź opis zdjęcia tutaj

Simon_Weaver
źródło
13

W moim przypadku konwertowałem tablicę dużych bajtów na ciąg znaków, używając:

String.fromCharCode.apply(null, new Uint16Array(bytes))

bytes zawiera kilka milionów wpisów, które są zbyt duże, aby zmieścić się na stosie.

Nate Glenn
źródło
Ja też miałem ten sam problem z tą samą linią !! thanx
ksh,
więc jakie jest najlepsze rozwiązanie do konwersji dużej tablicy bajtów?
ksh
6
@KarthikHande Musisz to zrobić w pętli for zamiast w jednym wywołaniu. var uintArray = new Uint16Array(bytes); var converted = []; uintArray.forEach(function(byte) {converted.push(String.fromCharCode(byte))});
Nate Glenn,
jak przekonwertować to na JSON? Próbowałem JSON.parse, ale to nie działało ... Używam Uintl8Array
ksh
@KarthikHande Nie mogę powiedzieć, nie widząc całego problemu. Proszę otworzyć kolejne pytanie ze wszystkimi szczegółami.
Nate Glenn
6

W moim przypadku zdarzenie click propagowało się na elemencie potomnym. Musiałem więc powiedzieć:

e.stopPropagation ()

na zdarzenie kliknięcia:

 $(document).on("click", ".remove-discount-button", function (e) {
           e.stopPropagation();
           //some code
        });
 $(document).on("click", ".current-code", function () {
     $('.remove-discount-button').trigger("click");
 });

Oto kod HTML:

 <div class="current-code">                                      
      <input type="submit" name="removediscountcouponcode" value="
title="Remove" class="remove-discount-button">
   </div>
Shahdat
źródło
5

Sprawdź szczegółowe informacje o błędzie w konsoli paska narzędzi Chrome dla programistów, to da ci funkcje na stosie wywołań i poprowadzi cię do rekurencji, która powoduje błąd.

chim
źródło
3

Prawie każda odpowiedź tutaj stwierdza, że ​​może to być spowodowane tylko nieskończoną pętlą. To nie jest prawda, w przeciwnym razie można by przekroczyć stos poprzez głęboko zagnieżdżone wywołania (nie mówiąc, że jest to wydajne, ale z pewnością jest to możliwe). Jeśli masz kontrolę nad maszyną wirtualną JavaScript, możesz dostosować rozmiar stosu. Na przykład:

node --stack-size=2000

Zobacz także: Jak zwiększyć maksymalny rozmiar stosu wywołań w Node.js

Ghayes
źródło
2

W moim przypadku dwa moduły jQuery były ułożone jeden na drugim. Zapobieganie to rozwiązało mój problem.

Barry Guvenkaya
źródło
2

Niedawno dodaliśmy pole do strony administratora, nad którą pracujemy - typ_kontaktu ... łatwe, prawda? Cóż, jeśli wywołasz select „typ” i spróbujesz wysłać go przez wywołanie ajax jquery, to nie powiedzie się z tym błędem ukrytym głęboko w jquery.js Nie rób tego:

$.ajax({
    dataType: "json",
    type: "POST",
    url: "/some_function.php",
    data: { contact_uid:contact_uid, type:type }
});

Problem polega na tym, że typ: typ - myślę, że to my nazywamy argument „typ” - posiadanie zmiennej wartości o nazwie typ nie jest problemem. Zmieniliśmy to na:

$.ajax({
    dataType: "json",
    type: "POST",
    url: "/some_function.php",
    data: { contact_uid:contact_uid, contact_type:type }
});

I odpowiednio przepisałem some_function.php - problem rozwiązany.

Scott
źródło
1

Sprawdź, czy masz funkcję, która się wywołuje. Na przykład

export default class DateUtils {
  static now = (): Date => {
    return DateUtils.now()
  }
}
onmyway133
źródło
1

Może to również powodować Maximum call stack size exceededbłąd:

var items = [];
[].push.apply(items, new Array(1000000)); //Bad

To samo tutaj:

items.push(...new Array(1000000)); //Bad

Z Dokumentów Mozilla :

Ale uwaga: używając tej opcji, ryzykujesz przekroczenie limitu długości argumentów silnika JavaScript. Konsekwencje zastosowania funkcji z zbyt dużą liczbą argumentów (pomyśl więcej niż dziesiątki tysięcy argumentów) są różne w różnych silnikach (JavaScriptCore ma zakodowany na stałe limit argumentów 65536), ponieważ limit (a nawet charakter każdego nadmiernie dużego stosu zachowanie) jest nieokreślony. Niektóre silniki zgłoszą wyjątek. Bardziej szkodliwe jest, że inni dowolnie ograniczą liczbę argumentów faktycznie przekazanych do zastosowanej funkcji. Aby zilustrować ten ostatni przypadek: gdyby taki silnik miał limit czterech argumentów (rzeczywiste limity są oczywiście znacznie wyższe), byłoby tak, jakby argumenty 5, 6, 2, 3 zostały przekazane do zastosowania w powyższych przykładach, zamiast pełnej tablicy.

Więc spróbuj:

var items = [];
var newItems = new Array(1000000);
for(var i = 0; i < newItems.length; i++){
  items.push(newItems[i]);
}
bendytree
źródło
0

Oba wywołania tego samego kodu poniżej, jeśli zostaną zmniejszone o 1, działają w Chrome 32 na moim komputerze, np. 17905 vs. 17904. Jeśli zostaną uruchomione, spowodują błąd „RangeError: Przekroczono maksymalny rozmiar stosu wywołań”. Wydaje się, że ten limit nie jest zakodowany na stałe, ale zależy od sprzętu twojego komputera. Wygląda na to, że jeśli zostanie wywołany jako funkcja, to narzucony przez siebie limit jest wyższy niż przy wywołaniu jako metoda, tzn. Ten konkretny kod zużywa mniej pamięci po wywołaniu jako funkcja.

Wywoływany jako metoda:

var ninja = {
    chirp: function(n) {
        return n > 1 ? ninja.chirp(n-1) + "-chirp" : "chirp";
    }
};

ninja.chirp(17905);

Wywoływany jako funkcja:

function chirp(n) {
    return n > 1 ? chirp( n - 1 ) + "-chirp" : "chirp";
}

chirp(20889);
Elijah Lynn
źródło
0

możesz znaleźć swoją funkcję rekurencyjną w przeglądarce crome, naciśnij ctrl + shift + j, a następnie kartę źródłową, co daje przepływ kompilacji kodu i możesz znaleźć za pomocą punktu przerwania w kodzie.

Vishal Patel
źródło
Czasami jednak trudno jest ustalić, skąd wziął się błąd. Na naszej stronie mamy dużo JavaScript.
Mathias Lykkegaard Lorenzen
0

Napotkałem również podobny problem, tutaj są szczegóły podczas przesyłania logo za pomocą rozwijanego pola przesyłania logo

<div>
      <div class="uploader greyLogoBox" id="uploader" flex="64" onclick="$('#filePhoto').click()">
        <img id="imageBox" src="{{ $ctrl.companyLogoUrl }}" alt=""/>
        <input type="file" name="userprofile_picture"  id="filePhoto" ngf-select="$ctrl.createUploadLogoRequest()"/>
        <md-icon ng-if="!$ctrl.isLogoPresent" class="upload-icon" md-font-set="material-icons">cloud_upload</md-icon>
        <div ng-if="!$ctrl.isLogoPresent" class="text">Drag and drop a file here, or click to upload</div>
      </div>
      <script type="text/javascript">
          var imageLoader = document.getElementById('filePhoto');
          imageLoader.addEventListener('change', handleImage, false);

          function handleImage(e) {
              var reader = new FileReader();
              reader.onload = function (event) {

                  $('.uploader img').attr('src',event.target.result);
              }
              reader.readAsDataURL(e.target.files[0]);
          }
      </script>
      </div>

CSS.css

.uploader {
  position:relative;
  overflow:hidden;
  height:100px;
  max-width: 75%;
  margin: auto;
  text-align: center;

  img{
    max-width: 464px;
    max-height: 100px;
    z-index:1;
    border:none;
  }

  .drag-drop-zone {
    background: rgba(0, 0, 0, 0.04);
    border: 1px solid rgba(0, 0, 0, 0.12);
    padding: 32px;
  }
}

.uploader img{
  max-width: 464px;
  max-height: 100px;
  z-index:1;
  border:none;
}



.greyLogoBox {
  width: 100%;
  background: #EBEBEB;
  border: 1px solid #D7D7D7;
  text-align: center;
  height: 100px;
  padding-top: 22px;
  box-sizing: border-box;
}


#filePhoto{
  position:absolute;
  width:464px;
  height:100px;
  left:0;
  top:0;
  z-index:2;
  opacity:0;
  cursor:pointer;
}

przed korektą mój kod był:

function handleImage(e) {
              var reader = new FileReader();
              reader.onload = function (event) {
                  onclick="$('#filePhoto').click()"
                  $('.uploader img').attr('src',event.target.result);
              }
              reader.readAsDataURL(e.target.files[0]);
          }

Błąd w konsoli:

wprowadź opis zdjęcia tutaj

Rozwiązałem go, usuwając onclick="$('#filePhoto').click()"z tagu div.

spacedev
źródło
Gdzie dodałeś click ()
Tsea
0

Miałem do czynienia z tym samym problemem, który rozwiązałem, usuwając nazwę pola, która była używana dwukrotnie w ajax np

    jQuery.ajax({
    url : '/search-result',
    data : {
      searchField : searchField,
      searchFieldValue : searchField,
      nid    :  nid,
      indexName : indexName,
      indexType : indexType
    },
.....
Tajdar Khan Afridi
źródło
0

Wiem, że ten wątek jest stary, ale myślę, że warto wspomnieć o scenariuszu, w którym znalazłem ten problem, aby mógł pomóc innym.

Załóżmy, że masz takie elementy zagnieżdżone:

<a href="#" id="profile-avatar-picker">
    <span class="fa fa-camera fa-2x"></span>
    <input id="avatar-file" name="avatar-file" type="file" style="display: none;" />
</a>

Nie można manipulować zdarzeniami elementu potomnego w zdarzeniu jego elementu nadrzędnego, ponieważ propaguje się ono do siebie, wykonując wywołania rekurencyjne, dopóki wyjątek nie zostanie zgłoszony.

Więc ten kod zawiedzie:

$('#profile-avatar-picker').on('click', (e) => {
    e.preventDefault();

    $('#profilePictureFile').trigger("click");
});

Masz dwie opcje, aby tego uniknąć:

  • Przenieś dziecko na zewnątrz rodzica.
  • Zastosuj funkcję stopPropagation do elementu potomnego.
Weslley Ramos
źródło
0

Miałem ten błąd, ponieważ miałem dwie funkcje JS o tej samej nazwie

lodowy smok
źródło
0

Jeśli pracujesz z mapami Google, sprawdź, czy są one przekazywane w new google.maps.LatLngodpowiednim formacie. W moim przypadku były one uznawane za niezdefiniowane.

Użytkownik-8017771
źródło
0

Problem w moim przypadku polega na tym, że mam trasy dzieci z tą samą ścieżką z rodzicem:

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    children: [
      { path: '', redirectTo: 'home', pathMatch: 'prefix' },
      { path: 'home', loadChildren: './home.module#HomeModule' },
    ]
  }
];

Musiałem więc usunąć linię z trasy dla dzieci

const routes: Routes = [
  {
    path: '',
    component: HomeComponent,
    children: [
      { path: 'home', loadChildren: './home.module#HomeModule' },
    ]
  }
];
Amine Harbaoui
źródło
0

w moim przypadku otrzymuję ten błąd przy wywołaniu ajax, a dane, które próbowałem przekazać, nie zostały zdefiniowane, co pokazuje mi ten błąd, ale nie opisuję tej zmiennej, która nie została zdefiniowana. Dodałem zdefiniowane, że zmienna n ma wartość.

asifaftab87
źródło
sam zostałem spalony w podobny sposób, w moim przypadku było tak dlatego, że wpisałem zmienną nazwę ... właściwie to samo.
user2366842,
0

Dotarłem do tego samego problemu, nie mogłem zrozumieć, co jest nie tak, że zaczął obwiniać Babel;)

Kod nie zwraca żadnych wyjątków w przeglądarkach:

if (typeof document.body.onpointerdown !== ('undefined' || null)) {

problem został źle utworzony || (lub) część, ponieważ babel tworzy własną kontrolę typu:

function _typeof(obj){if(typeof Symbol==="function"&&_typeof(Symbol.iterator)==="symbol")

więc usuwam

|| null

sprawiło, że transpilacja babel działała.

Raphael
źródło
0

W moim przypadku przez pomyłkę przypisałem tę samą nazwę zmiennej i do funkcji val „class_routine_id”

var class_routine_id = $("#class_routine_id").val(class_routine_id);

powinno być jak:

 var class_routine_id = $("#class_routine_id").val(); 
Alok Jha
źródło
0

Używam React-Native 0.61.5 wraz z ( npm 6.9.0 i węzeł 10.16.1 )

Podczas gdy instaluję nowe biblioteki w Project, mam

(np. npm install @ reag-navigation / native --save)

Błąd maksymalnego rozmiaru stosu wywołań

do tego próbuję

sudo npm cache clean --force

(Uwaga: - Poniższe polecenie zwykle zajmuje od 1 do 2 minut w zależności od wielkości pamięci podręcznej npm )

DevAelius
źródło
-1

Czasami zdarzało się z powodu konwersji typu danych, na przykład masz obiekt, który uważałeś za ciąg znaków.

socket.id w nodejs w kliencie js jako przykład nie jest łańcuchem. aby użyć go jako ciągu, musisz dodać słowo Ciąg przed:

String(socket.id);
Hussein Mahjoub
źródło
-1

Próbowałem przypisać zmienną, wartość, gdy ta zmienna nie została zadeklarowana.

Zadeklarowanie zmiennej naprawiło mój błąd.

Phillip Quinlan
źródło