QuotaExceededError: Dom wyjątek 22: Podjęto próbę dodania do pamięci czegoś, co przekroczyło limit

219

Korzystanie z LocalStorage na iPhonie z iOS 7 powoduje zgłoszenie tego błędu. Rozglądałem się za rezolucją, ale biorąc pod uwagę, że nawet nie przeglądam na osobności, nic nie jest istotne.

Nie rozumiem, dlaczego localStorage byłby domyślnie wyłączony w iOS 7, ale wydaje się, że tak jest? Testowałem również na innych stronach internetowych, ale bez powodzenia. Próbowałem nawet przetestować go przy użyciu tej witryny: http://arty.name/localstorage.html , ale nie wydaje się, żeby coś w ogóle zapisywało z jakiegoś dziwnego powodu.

Czy ktoś miał ten sam problem, tylko czy udało mu się go naprawić? Czy powinienem zmienić metodę przechowywania?

Próbowałem go mocno debugować, przechowując tylko kilka wierszy informacji, ale bezskutecznie. Użyłem standardowej localStorage.setItem()funkcji do zapisania.

Nict
źródło
2
Zazwyczaj oznacza to, że próbowałeś przechowywać coś o rozmiarze przekraczającym dostępną przestrzeń dyskową. Z jakiej przeglądarki korzystasz (Safari, Chrome itp.)? Czy możesz udostępnić trochę więcej kodu, którego używałeś i, jeśli to możliwe, dane, które próbujesz przechowywać.
3
Należy to uznać za błąd lub problem po stronie Safari. Nie ma sensu, abyś nie mógł używać localStorage w trybie incognito ...
Maksim Luzik
Użyj funkcji wykrywania tego testu dla tego konkretnego problemu . Jeśli pamięć nie jest dostępna, rozważ shims localStorage z memoryStorage . zrzeczenie się odpowiedzialności: jestem autorem powiązanych pakietów
Stijn de Witt
1
W kwietniu 2017 r. Łatka została połączona z Safari, więc była zgodna z innymi przeglądarkami. Prawdopodobnie wyląduje w Safari 11. bugs.webkit.org/show_bug.cgi?id=157010
sandstrom
2
Mogę potwierdzić, że zostało to naprawione w Safari iOS 11. Testowane Prywatne przeglądanie + sessionStorage.setItem (), a następnie sessionStorage.getItem () z powodzeniem na iPhone6 ​​i iPhone8.
Kevin Gaudin

Odpowiedzi:

372

Może się to zdarzyć, gdy Safari jest w trybie prywatnym. Podczas przeglądania prywatnego pamięć lokalna nie jest w ogóle dostępna.

Jednym z rozwiązań jest ostrzeżenie użytkownika, że ​​aplikacja potrzebuje trybu nieprywatnego do działania.

AKTUALIZACJA: Zostało to naprawione w Safari 11 , więc zachowanie jest teraz dostosowane do innych przeglądarek.

Cristian Dinu
źródło
4
Twój post był dla mnie dzisiaj niezwykle pomocny i aktualny (mniej niż 24 godziny później). Dla porównania, oto jak włączyć / wyłączyć prywatne przeglądanie: imore.com/how-use-private-browsing-ios-7-safari
Nick
12
+1 naprawiło mój problem. Sprawdzałem istnienie LocalStorage ( if( typeof Storage != 'undefined' ) { ... }) przed próbą załadowania i zapisania informacji, ale otrzymałem ten błąd. Okazuje się, że Storagenadal jest zdefiniowane, nawet jeśli nie można go użyć. Używanie try / catch od teraz za każdym razem, gdy korzystam z LocalStorage.
stevendesu
Dzięki! Dziwny błąd przez safari. Powinny być bardziej pouczające. : D
Sunny R Gupta
2
Może pojawić się poprawka od Safari Tech Preview 29: „Naprawiono błąd QuotaExceededError podczas zapisywania do localStorage w trybie przeglądania prywatnego lub sesji WebDriver”. Zobacz developer.apple.com/safari/technology-preview/release-notes
Marc Baumbach
1
Może się to również zdarzyć, gdy zostanie osiągnięty limit pamięci, co można łatwo zrobić, np. Zapisując obrazy.
csalmeida
103

Jak wspomniano w innych odpowiedziach, zawsze otrzymasz błąd QuotaExceededError w trybie prywatnej przeglądarki Safari na iOS i OS X, gdy localStorage.setItem(lubsessionStorage.setItem wywoływany jest ).

Jednym z rozwiązań jest sprawdzenie try / catch lub Modernizr przy każdym użyciu setItem.

Jeśli jednak chcesz zastosować podkładkę dystansową, która po prostu globalnie zatrzymuje generowanie tego błędu, aby zapobiec uszkodzeniu reszty kodu JavaScript, możesz użyć tego:

https://gist.github.com/philfreo/68ea3cd980d72383c951

// Safari, in Private Browsing Mode, looks like it supports localStorage but all calls to setItem
// throw QuotaExceededError. We're going to detect this and just silently drop any calls to setItem
// to avoid the entire page breaking, without having to do a check at each usage of Storage.
if (typeof localStorage === 'object') {
    try {
        localStorage.setItem('localStorage', 1);
        localStorage.removeItem('localStorage');
    } catch (e) {
        Storage.prototype._setItem = Storage.prototype.setItem;
        Storage.prototype.setItem = function() {};
        alert('Your web browser does not support storing settings locally. In Safari, the most common cause of this is using "Private Browsing Mode". Some settings may not save or some features may not work properly for you.');
    }
}
philfreo
źródło
1
Po co dodawać setItem do obiektu Storage, jeśli i tak nie będzie można go użyć?
Nekromanta
4
Celem mojego fragmentu jest po prostu zignorowanie błędów JS przed wyrzuceniem, jeśli chcesz, aby Twoja aplikacja nie uległa całkowitemu uszkodzeniu w trybie prywatnym Safari.
philfreo
16

Korzystam z tej prostej funkcji, która zwraca truelub false, w celu przetestowania dostępności localStorage:

isLocalStorageNameSupported = function() {
    var testKey = 'test', storage = window.sessionStorage;
    try {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return true;
    } catch (error) {
        return false;
    }
}

Teraz możesz przetestować localStorage.setItem()dostępność przed użyciem. Przykład:

if ( isLocalStorageNameSupported() ) {
    // can use localStorage.setItem('item','value')
} else {
    // can't use localStorage.setItem('item','value')
}
DrewT
źródło
Czy coś przeoczyłem? Dlaczego window.sessionStoragezastosowano window.localStoragemetodę zamiast o nazwie isLocalStorageNameSupported?
Ithar
@lthar - patrz dokumentacja tutaj: w3schools.com/html/html5_webstorage.asp Co najważniejsze, ta część:HTML local storage provides two objects for storing data on the client: window.localStorage - stores data with no expiration date window.sessionStorage - stores data for one session (data is lost when the browser tab is closed)
DrewT
@DrewT, ale jaka jest różnica w tej sytuacji, jeśli usuniesz klucz testowy? Nie ma znaczenia, gdzie będę przechowywać klucz testowy, jeśli mam go usunąć. Czy się mylę? Dlaczego pamięć sesji jest lepsza niż pamięć lokalna?
Vladyslav Turak,
1
@TurakVladyslav masz rację, naprawdę nie ma tutaj żadnej różnicy, z wyjątkiem tego, że korzystanie z sessionStorageniej ułatwia zarządzanie punktami przerwania, jeśli chcesz przetestować swój rozwój. Nie ma prawdziwego argumentu, który byłby „lepszy”, a tak naprawdę to tylko osobiste preferencje, które popełniają błąd po stronie ostrożności. Najważniejszą rzeczą jest, aby pamiętać, że oba sessionStoragei localStoragesą obie implementacje API HTML5 WebStorage.
DrewT
5

Zdarzyło mi się działać z tym samym problemem w iOS 7 (na niektórych urządzeniach nie ma symulatorów).

Wygląda na to, że Safari w iOS 7 ma niższy limit miejsca, który najwyraźniej jest osiągnięty przez długi dziennik historii.

Chyba najlepszą praktyką będzie złapanie wyjątku.

Projekt Modernizr ma łatwą łatkę, powinieneś spróbować czegoś podobnego: https://github.com/Modernizr/Modernizr/blob/master/feature-detects/storage/localstorage.js

defvol
źródło
3

Oto rozszerzone rozwiązanie oparte na powyższej odpowiedzi DrewT, które wykorzystuje pliki cookie, jeśli localStorage nie jest dostępny. Korzysta z biblioteki docCookies Mozilli :

function localStorageGet( pKey ) {
    if( localStorageSupported() ) {
        return localStorage[pKey];
    } else {
        return docCookies.getItem( 'localstorage.'+pKey );
    }
}

function localStorageSet( pKey, pValue ) {
    if( localStorageSupported() ) {
        localStorage[pKey] = pValue;
    } else {
        docCookies.setItem( 'localstorage.'+pKey, pValue );
    }
}

// global to cache value
var gStorageSupported = undefined;
function localStorageSupported() {
    var testKey = 'test', storage = window.sessionStorage;
    if( gStorageSupported === undefined ) {
        try {
            storage.setItem(testKey, '1');
            storage.removeItem(testKey);
            gStorageSupported = true;
        } catch (error) {
            gStorageSupported = false;
        }
    }
    return gStorageSupported;
}

W swoim źródle po prostu użyj:

localStorageSet( 'foobar', 'yes' );
...
var foo = localStorageGet( 'foobar' );
...
Stickley
źródło
2

Jak już wyjaśniono w innych odpowiedziach, w trybie przeglądania prywatnego Safari zawsze zgłasza ten wyjątek przy próbie zapisania danych localStorage.setItem().

Aby to naprawić, napisałem fałszywy localStorage, który naśladuje localStorage, zarówno metody, jak i zdarzenia.

Fałszywy lokalny magazyn: https://gist.github.com/engelfrost/fd707819658f72b42f55

Prawdopodobnie nie jest to dobre ogólne rozwiązanie problemu. To było dobre rozwiązanie dla mojego scenariusza, w którym alternatywą byłoby poważne ponowne zapisanie do już istniejącej aplikacji.

Josef Engelfrost
źródło
Co to naprawia? Niczego nie utrzymuje, więc o co chodzi?
Esben Skov Pedersen
1
„Naprawia” Safari w trybie przeglądania prywatnego. (Nie jest to jasne w mojej odpowiedzi, dziękuję za zwrócenie na to uwagi. Zredaguję moją odpowiedź). Niezależnie od tego, w trybie przeglądania prywatnego nic nie powinno być utrwalane, więc nietrwałość nie jest tutaj istotnym problemem. To, co naprawiłem, polegało na umożliwieniu użytkownikom uruchamiania już istniejącej aplikacji, bez większych ponownych zapisów, nawet w trybie przeglądania prywatnego w przeglądarce Safari.
Josef Engelfrost
2

Aktualizacja (01.11.2016)

Użyłem wspomnianego poniżej AmplifyJS do obejścia tego problemu. Jednak w przypadku przeglądarki Safari w trybie prywatnym wracała do pamięci opartej na pamięci. W moim przypadku nie było to właściwe, ponieważ oznacza to, że pamięć jest czyszczona podczas odświeżania, nawet jeśli użytkownik nadal korzysta z prywatnego przeglądania.

Zauważyłem również wielu użytkowników, którzy zawsze przeglądają w trybie prywatnym na iOS Safari. Z tego powodu lepszym rozwiązaniem dla Safari jest użycie plików cookie (jeśli są dostępne). Domyślnie pliki cookie są nadal dostępne nawet podczas prywatnego przeglądania. Oczywiście są one usuwane podczas wychodzenia z prywatnego przeglądania, ale nie są usuwane podczas odświeżania.

Znalazłem bibliotekę rezerwową lokalnego magazynu . Z dokumentacji:

Cel, powód

Przy ustawieniach przeglądarki, takich jak „Prywatne przeglądanie”, problemem stało się poleganie na działającym window.localStorage, nawet w nowszych przeglądarkach. Mimo że może istnieć, zgłasza wyjątki podczas próby użycia setItem lub getItem. Ten moduł uruchomi odpowiednie kontrole, aby sprawdzić, jaki mechanizm pamięci przeglądarki może być dostępny, a następnie go ujawni. Używa tego samego interfejsu API co localStorage, więc w większości przypadków powinien działać jako zastępczy drop-in.

Uwaga na gotchas:

  • CookieStorage ma limity przechowywania. Uważaj tutaj.
  • MemoryStorage nie będzie trwał między ładowaniami stron. Jest to mniej więcej przerwa, aby zapobiec awariom strony, ale może być wystarczająca w przypadku witryn, które nie ładują całej strony.

TL; DR:

Użyj rezerwy lokalnej pamięci masowej (ujednolicony interfejs API z .getItem(prop)i .setItem(prop, val)):

Sprawdź i użyj odpowiedniego adaptera pamięci dla przeglądarki (localStorage, sessionStorage, cookies, pamięć)

Oryginalna odpowiedź

Aby dodać poprzednie odpowiedzi, jednym z możliwych obejść może być zmiana metody przechowywania. Istnieje kilka librairies, takich jak AmplifyJS i PersistJS które mogą pomóc. Obie biblioteki pozwalają na trwałe przechowywanie po stronie klienta przez kilka backendów.

Dla AmplifyJS

Lokalny magazyn

  • IE 8+
  • Firefox 3.5+
  • Safari 4+
  • Chrom
  • Opera 10.5+
  • iPhone 2+
  • Android 2+

sessionStorage

  • IE 8+
  • Firefox 2+
  • Safari 4+
  • Chrom
  • Opera 10.5+
  • iPhone 2+
  • Android 2+

globalStorage

  • Firefox 2+

dane użytkownika

  • IE 5 - 7
  • userData istnieje również w nowszych wersjach IE, ale z powodu dziwactw w implementacji IE 9, nie rejestrujemy userData, jeśli jest obsługiwany localStorage.

pamięć

  • Magazyn w pamięci jest zastępczy, jeśli żaden z pozostałych typów pamięci nie jest dostępny.

For PersistentJS

  • flash: Pamięć trwała Flash 8.
  • koła zębate: trwałe miejsce do przechowywania oparte na Google Gears.
  • localstorage: Przechowywanie wersji roboczej HTML5.
  • globalstorage: Przechowywanie wersji roboczej HTML5 (stara specyfikacja).
  • tj. zachowania danych użytkownika w Internet Explorerze.
  • cookie: trwałe przechowywanie na podstawie plików cookie.

Oferują warstwę abstrakcji, więc nie musisz się martwić o wybór rodzaju pamięci. Pamiętaj jednak, że mogą istnieć pewne ograniczenia (takie jak limity wielkości) w zależności od rodzaju pamięci. W tej chwili używam AmplifyJS, ale nadal muszę wykonać więcej testów na iOS 7 / Safari / etc. aby sprawdzić, czy to faktycznie rozwiązuje problem.

Jonathan Alzetta
źródło
Redaktor John: Zdaję sobie sprawę, że ty i Jonathan Alzetta jesteś prawdopodobnie tym samym kontem, a ty po prostu próbujesz poprawić swoją odpowiedź, ale jeśli tak, to naprawdę powinieneś zalogować się jako Jonathan Alzetta i edytować tę odpowiedź, a wtedy nie przejdzie kolejka recenzji. Odzyskaj swoje konto, jeśli potrzebujesz.
DavidS
0

To pytanie i odpowiedź pomogły mi rozwiązać konkretny problem z rejestracją nowych użytkowników w Parse.

Ponieważ funkcja signUp (attrs, options) używa pamięci lokalnej do utrwalenia sesji, jeśli użytkownik jest w trybie przeglądania prywatnego, zgłasza „QuotaExceededError: DOM Exception 22: Podjęto próbę dodania do pamięci czegoś, co przekroczyło limit”. wyjątek i funkcje powodzenia / błędu nigdy nie są wywoływane.

W moim przypadku, ponieważ funkcja błędu nigdy nie jest wywoływana, początkowo wydawało się, że jest to problem z odpaleniem zdarzenia kliknięcia w przesłanym pliku lub przekierowaniem zdefiniowanym po pomyślnym zarejestrowaniu.

Dołączenie ostrzeżenia dla użytkowników rozwiązało problem.

Analiza składni SDK JavaScript https://parse.com/docs/js/api/classes/Parse.User.html#methods_signUp

Rejestruje nowego użytkownika za pomocą nazwy użytkownika (lub adresu e-mail) i hasła. Spowoduje to utworzenie nowego Parse.User na serwerze, a także utrwalenie sesji w localStorage , abyś mógł uzyskać dostęp do użytkownika za pomocą {@link #current}.

clayostrom
źródło