html5 localStorage ошибка с Safari: «QUOTA_EXCEEDED_ERR: DOM Exception 22: была сделана попытка добавить что-то в хранилище, которое превысило квоту».

133

В моем веб-приложении есть ошибки javascript в приватном просмотре ios safari:

JavaScript: ошибка

не определено

QUOTA_EXCEEDED_ERR: DOM Exception 22: была сделана попытка добавить что-то в хранилище ...

мой код:

localStorage.setItem('test',1)
leiyonglin
источник
Используйте функцию обнаружения этого теста для этой конкретной проблемы . Если хранилище недоступно, рассмотрите возможность использования localStorage с помощью memoryStorage . Отказ от ответственности: я являюсь автором связанных пакетов
Stijn de Witt
4
Привет, ребята, я помогаю поддерживать Safaridriver. Эта проблема - давняя ошибка в WebKit, которая была недавно исправлена. Локальное хранилище и хранилище сеансов теперь работают в Safari 10.1 и более поздних версиях. Это исправление влияет на обычный режим частного просмотра и режим автоматизации (используется WebDriver).
Брайан Бург

Ответы:

183

Видимо это по замыслу. Когда Safari (OS X или iOS) находится в режиме частного просмотра, он выглядит как localStorageдоступный, но попытка вызова setItemвызывает исключение.

store.js line 73
"QUOTA_EXCEEDED_ERR: DOM Exception 22: An attempt was made to add something to storage that exceeded the quota."

То, что происходит, - то, что объект окна все еще выставляется localStorageв глобальном пространстве имен, но когда вы вызываете setItem, это исключение выдается. Любые звонки removeItemигнорируются.

Я считаю, что самое простое исправление (хотя я еще не тестировал этот кросс-браузер) - изменить функцию, isLocalStorageNameSupported()чтобы проверить, что вы также можете установить какое-то значение.

https://github.com/marcuswestin/store.js/issues/42

function isLocalStorageNameSupported() 
{
    var testKey = 'test', storage = window.sessionStorage;
    try 
    {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return localStorageName in win && win[localStorageName];
    } 
    catch (error) 
    {
        return false;
    }
}
KingKongFrog
источник
1
Это не обязательно должно быть связано с режимом инкогнито ... хотя я полагаю, что ОП не хотел хранить несколько мегабайт данных;)
Кристоф,
5
Посмотрите эту суть, показывающую краткую историю обнаружения локального хранилища Пола Айриша.
Мотти
4
Таким образом, в случае, если localStorage не будет работать в Safari, является ли лучшим вариантом сохранение всего в файлах cookie?
Уилл Хичкок
5
Как и в примере с ирландским Полом, я предлагаю изменить return localStorageName in win && win[localStorageName];на return true. Тогда у вас есть функция, которая безопасно возвращает истину или ложь в зависимости от доступности localStorage. Например:if (isLocalStorageNameSupported()) { /* You can use localStorage.setItem */ } else { /* you can't use localStorage.setItem */ }
DrewT
1
Проверено, что проблема не только с приватным окном, но и с обычным окном сафари.
codemirror
38

Исправление, размещенное по вышеуказанной ссылке, не работает для меня. Это сделал:

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

Получено из http://m.cg/post/13095478393/detect-private-browsing-mode-in-mobile-safari-on-ios5

cyberwombat
источник
20
По какой-то конкретной причине вы (и @KingKongFrog) используете window.sessionStorage, чтобы определить, можете ли вы писать в localStorage, или мы находимся в странном цикле опечаток копирования-вставки?
Йети
@ Да, если вы заметили опечатку, почему бы вам не исправить ее в редактировании или в своем комментарии? Насколько я знаю, window.sessionStorageэто правильно. Это конечно работает в моем коде. Пожалуйста , на самом деле указывают на исправление этой проблемы вы оказываетесь знать.
Новокаин
7
@Novocaine В моем комментарии было указано, что они используют sessionStorage в функции, которая существует для проверки поддержки localStorage. Да, скорее всего, он все еще будет работать, но, как написано, имя функции вводит в заблуждение относительно того, что на самом деле тестируется. Я решил комментировать, а не редактировать, потому что я думал, что что-то упустил и надеялся учиться у этих парней. К сожалению, они не ответили и не внесли исправление, поэтому мы здесь.
Йетти
3
@ Yetti Спасибо за разъяснения. Я вижу, о чем ты был сейчас. ; -]
Новокаин
2
@ DawsonToth нет, потому что я вызвал функцию isLocalStorageNameSupportedи проверял window.sessionStorage. Тот же конечный результат, но был немного запутанным. Ответ был отредактирован для уточнения.
cyberwombat
25

Как упоминалось в других ответах, вы всегда получите QuotaExceededError в режиме приватного браузера Safari на iOS и OS X при вызове localStorage.setItem(или sessionStorage.setItem).

Одним из решений является проверка try / catch или Modernizr в каждом случае использования setItem.

Однако, если вы хотите, чтобы шим, который просто глобально останавливал эту ошибку, чтобы предотвратить поломку остальной части вашего JavaScript, вы можете использовать это:

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
источник
11

В моем контексте только что разработали абстракцию класса. Когда мое приложение запускается, я проверяю, работает ли localStorage, вызывая getStorage () . Эта функция также возвращает:

  • либо localStorage, если localStorage работает
  • или реализация пользовательского класса LocalStorageAlternative

В моем коде я никогда не вызываю localStorage напрямую. Я вызываю глобальную переменную cusSto , которую я инициализировал, вызвав getStorage () .

Таким образом, он работает с приватным просмотром или определенными версиями Safari.

function getStorage() {

    var storageImpl;

     try { 
        localStorage.setItem("storage", ""); 
        localStorage.removeItem("storage");
        storageImpl = localStorage;
     }
     catch (err) { 
         storageImpl = new LocalStorageAlternative();
     }

    return storageImpl;

}

function LocalStorageAlternative() {

    var structureLocalStorage = {};

    this.setItem = function (key, value) {
        structureLocalStorage[key] = value;
    }

    this.getItem = function (key) {
        if(typeof structureLocalStorage[key] != 'undefined' ) {
            return structureLocalStorage[key];
        }
        else {
            return null;
        }
    }

    this.removeItem = function (key) {
        structureLocalStorage[key] = undefined;
    }
}

cusSto = getStorage();
Пьер Ле Ру
источник
2
Спасибо, Пьер, твой ответ вдохновил меня. Я закончил упаковывать все это в хороший модуль под названием memorystorage . Открытый источник, конечно. Для других людей с той же проблемой, проверьте это, это может помочь вам.
Стейн де Витт
Ха. Я сделал то же самое (самостоятельно). Тем не менее, по-прежнему используйте переменную localStorage (по крайней мере, в Safari вы не можете перезаписать переменную localStorage (она «только для чтения»), но вы можете переназначить setItem / removeItem / getItem).
Рубен Мартинес младший
@StijndeWitt, как я могу получить доступ к своим хранилищам на других страницах? Например, у меня есть это в моем helper.php var store = MemoryStorage ('my-app'); store.setItem ('myString', 'Hello MemoryStorage!'); Я хочу получить доступ к значению myString в lecture.php. Я попытался запустить memorystorage на странице, но все равно он показывает пустой объект.
user1149244
@ user1149244 Memorystorage является локальным для страницы. Он имитирует API веб-хранилища и, таким образом, может использоваться в качестве запасного варианта, когда localStorage и sessionStorage недоступны. Однако данные будут храниться только в памяти страницы (отсюда и название). Если вам нужно сохранить данные на разных страницах, куки могут помочь вам. Но это очень ограниченный объем данных, которые могут быть сохранены. Кроме этого, мало что можно сделать.
Стейн де Витт
2
@ user1149244 Разве это не якобы хранить значения в браузере? Нет, не может. Существует 3 способа хранения вещей на стороне клиента от обновления одной страницы к другой: куки, sessionStorage / localStorage и IndexedDB. Последние два относительно новые. SessionStorage и localStorage широко поддерживаются, поэтому вы можете использовать его практически везде. кроме как в режиме приватного просмотра , о чем эта проблема. Программы сломались, потому что памяти там не было. memorystorage просто предоставляет запасной вариант, который всегда работает на странице, но на самом деле он не может сохранить данные. Это заглушка. Но без ошибок.
Стейн де Витт
5

Кажется, что Safari 11 меняет поведение, и теперь локальное хранилище работает в частном окне браузера. Ура!

Наше веб-приложение, которое раньше не работало в режиме приватного просмотра Safari, теперь работает безупречно. Он всегда хорошо работал в режиме частного просмотра Chrome, который всегда позволял записывать в локальное хранилище.

Это задокументировано в заметках о выпуске Apple Safari Technology Preview - и в заметках о выпуске WebKit - для выпуска 29, выпущенного в мае 2017 года.

В частности:

  • Исправлена ​​ошибка QuotaExceededError при сохранении в localStorage в режиме частного просмотра или сеансах WebDriver - r215315
karlbecker_com
источник
4

Чтобы расширить ответы других, вот компактное решение, которое не раскрывает / добавляет новые переменные. Он не охватывает все основы, но должен подходить большинству людей, которые хотят, чтобы одностраничное приложение оставалось работоспособным (несмотря на отсутствие сохранения данных после перезагрузки).

(function(){
    try {
        localStorage.setItem('_storage_test', 'test');
        localStorage.removeItem('_storage_test');
    } catch (exc){
        var tmp_storage = {};
        var p = '__unique__';  // Prefix all keys to avoid matching built-ins
        Storage.prototype.setItem = function(k, v){
            tmp_storage[p + k] = v;
        };
        Storage.prototype.getItem = function(k){
            return tmp_storage[p + k] === undefined ? null : tmp_storage[p + k];
        };
        Storage.prototype.removeItem = function(k){
            delete tmp_storage[p + k];
        };
        Storage.prototype.clear = function(){
            tmp_storage = {};
        };
    }
})();
Джон
источник
3

У меня была такая же проблема с использованием Ionic Framework (Angular + Cordova). Я знаю, что это не решает проблему, но это код для Angular Apps, основанный на ответах выше. У вас будет временное решение для localStorage в iOS-версии Safari.

Вот код:

angular.module('myApp.factories', [])
.factory('$fakeStorage', [
    function(){
        function FakeStorage() {};
        FakeStorage.prototype.setItem = function (key, value) {
            this[key] = value;
        };
        FakeStorage.prototype.getItem = function (key) {
            return typeof this[key] == 'undefined' ? null : this[key];
        }
        FakeStorage.prototype.removeItem = function (key) {
            this[key] = undefined;
        };
        FakeStorage.prototype.clear = function(){
            for (var key in this) {
                if( this.hasOwnProperty(key) )
                {
                    this.removeItem(key);
                }
            }
        };
        FakeStorage.prototype.key = function(index){
            return Object.keys(this)[index];
        };
        return new FakeStorage();
    }
])
.factory('$localstorage', [
    '$window', '$fakeStorage',
    function($window, $fakeStorage) {
        function isStorageSupported(storageName) 
        {
            var testKey = 'test',
                storage = $window[storageName];
            try
            {
                storage.setItem(testKey, '1');
                storage.removeItem(testKey);
                return true;
            } 
            catch (error) 
            {
                return false;
            }
        }
        var storage = isStorageSupported('localStorage') ? $window.localStorage : $fakeStorage;
        return {
            set: function(key, value) {
                storage.setItem(key, value);
            },
            get: function(key, defaultValue) {
                return storage.getItem(key) || defaultValue;
            },
            setObject: function(key, value) {
                storage.setItem(key, JSON.stringify(value));
            },
            getObject: function(key) {
                return JSON.parse(storage.getItem(key) || '{}');
            },
            remove: function(key){
                storage.removeItem(key);
            },
            clear: function() {
                storage.clear();
            },
            key: function(index){
                storage.key(index);
            }
        }
    }
]);

Источник: https://gist.github.com/jorgecasar/61fda6590dc2bb17e871

Приятного кодирования!

jorgecasar
источник
1
Хотя это не отвечает на вопрос, это первое, что пришло мне в голову, когда я погуглил вопрос. Следующим шагом было бы найти решение для Angular, но благодаря этому комментарию мне больше не нужно никуда идти. Так что, возможно, он не отвечает прямо на вопрос, но отлично подходит для меня и, вероятно, для других!
Леонард
2

Вот решение для AngularJS, использующее IIFE и использующее тот факт, что сервисы являются одиночными .

Это приводит к тому, isLocalStorageAvailableчто он устанавливается сразу же после первого запуска службы и позволяет избежать ненужного запуска проверки каждый раз, когда требуется доступ к локальному хранилищу.

angular.module('app.auth.services', []).service('Session', ['$log', '$window',
  function Session($log, $window) {
    var isLocalStorageAvailable = (function() {
      try {
        $window.localStorage.world = 'hello';
        delete $window.localStorage.world;
        return true;
      } catch (ex) {
        return false;
      }
    })();

    this.store = function(key, value) {
      if (isLocalStorageAvailable) {
        $window.localStorage[key] = value;
      } else {
        $log.warn('Local Storage is not available');
      }
    };
  }
]);
Пьер-Люк Жандро
источник
1

Я только что создал этот репозиторий, чтобы предоставить sessionStorageи localStorageфункции для неподдерживаемых или отключенных браузеров.

Поддерживаемые браузеры

  • IE5 +
  • Chrome все версии
  • Mozilla все версии
  • Яндекс все версии

Как это устроено

Он обнаруживает функцию с типом хранилища.

function(type) {
    var testKey = '__isSupported',
        storage = window[type];
    try {
        storage.setItem(testKey, '1');
        storage.removeItem(testKey);
        return true;
    } catch (error) {
        return false;
    }
};

Наборы StorageService.localStorageдля , window.localStorageесли она поддерживается или создает хранилище печенья. Наборы StorageService.sessionStorageдля , window.sessionStorageесли она поддерживается или создает в памяти хранения для SPA, хранения печенья с особенностями Sesion для отсутствия SPA.

Ахмет Кан Гювен
источник
1
Спасибо, ваша библиотека очень помогла!
Матье
1

Вот сервисная версия Angular2 + для альтернативы памяти, которую вы можете просто вставить в свои компоненты, основываясь на ответе Пьера Ле Ру.

import { Injectable } from '@angular/core';

// Alternative to localstorage, memory
// storage for certain browsers in private mode
export class LocalStorageAlternative {
    private  structureLocalStorage = {};

    setItem(key: string, value: string): void {
        this.structureLocalStorage[key] = value;
    }

    getItem(key: string): string {
        if (typeof this.structureLocalStorage[key] !== 'undefined' ) {
            return this.structureLocalStorage[key];
        }
        return null;
    }

    removeItem(key: string): void {
        this.structureLocalStorage[key] = undefined;
    }
}

@Injectable()
export class StorageService {
    private storageEngine;

    constructor() {
        try {
            localStorage.setItem('storage_test', '');
            localStorage.removeItem('storage_test');
            this.storageEngine = localStorage;
        } catch (err) {
            this.storageEngine = new LocalStorageAlternative();
        }
    }

    setItem(key: string, value: string): void {
        this.storageEngine.setItem(key, value);
    }

    getItem(key: string): string {
        return this.storageEngine.getItem(key);
    }

    removeItem(key: string): void {
        this.storageEngine.removeItem(key);
    }

}
Габриэль Алэк
источник
0

Не используйте его, если он не поддерживается, а для проверки поддержки просто вызовите эту функцию

совместное использование в Es6 полного чтения и записи LocalStorage Пример с проверкой поддержки

const LOCAL_STORAGE_KEY = 'tds_app_localdata';

const isSupported = () => {
  try {
    localStorage.setItem('supported', '1');
    localStorage.removeItem('supported');
    return true;
  } catch (error) {
    return false;
  }
};


const writeToLocalStorage =
  components =>
    (isSupported ?
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(components))
      : components);

const isEmpty = component => (!component || Object.keys(component).length === 0);

const readFromLocalStorage =
  () => (isSupported ? JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) || {} : null);

Это обеспечит правильную установку и получение ключей во всех браузерах.

Тарандип Сингх
источник
0

Я создал патч для этой проблемы. Просто я проверяю, поддерживает ли браузер localStorage или sessionStorage или нет. Если нет, то механизм хранения будет Cookie. Но отрицательная сторона - у Cookie очень крошечная память :(

function StorageEngine(engine) {
    this.engine = engine || 'localStorage';

    if(!this.checkStorageApi(this.engine)) {
        // Default engine would be alway cooke
        // Safari private browsing issue with localStorage / sessionStorage
        this.engine = 'cookie';
    }
}

StorageEngine.prototype.checkStorageApi = function(name) {
    if(!window[name]) return false;
    try {
        var tempKey = '__temp_'+Date.now();
        window[name].setItem(tempKey, 'hi')
        window[name].removeItem(tempKey);
        return true;
    } catch(e) {
        return false;
    }
}

StorageEngine.prototype.getItem = function(key) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        return window[this.engine].getItem(key);
    } else if('cookie') {
        var name = key+"=";
        var allCookie = decodeURIComponent(document.cookie).split(';');
        var cval = [];
        for(var i=0; i < allCookie.length; i++) {
            if (allCookie[i].trim().indexOf(name) == 0) {
                cval = allCookie[i].trim().split("=");
            }   
        }
        return (cval.length > 0) ? cval[1] : null;
    }
    return null;
}

StorageEngine.prototype.setItem = function(key, val, exdays) {
    if(['sessionStorage', 'localStorage'].includes(this.engine)) {
        window[this.engine].setItem(key, val);
    } else if('cookie') {
        var d = new Date();
        var exdays = exdays || 1;
        d.setTime(d.getTime() + (exdays*24*36E5));
        var expires = "expires="+ d.toUTCString();
        document.cookie = key + "=" + val + ";" + expires + ";path=/";
    }
    return true;
}


// ------------------------
var StorageEngine = new StorageEngine(); // new StorageEngine('localStorage');
// If your current browser (IOS safary or any) does not support localStorage/sessionStorage, then the default engine will be "cookie"

StorageEngine.setItem('keyName', 'val')

var expireDay = 1; // for cookie only
StorageEngine.setItem('keyName', 'val', expireDay)
StorageEngine.getItem('keyName')
Саддам Х
источник
0

Принятый ответ кажется неадекватным в нескольких ситуациях.

Чтобы проверить, поддерживаются ли localStorageили sessionStorageя, я использую следующий фрагмент из MDN .

function storageAvailable(type) {
    var storage;
    try {
        storage = window[type];
        var x = '__storage_test__';
        storage.setItem(x, x);
        storage.removeItem(x);
        return true;
    }
    catch(e) {
        return e instanceof DOMException && (
            // everything except Firefox
            e.code === 22 ||
            // Firefox
            e.code === 1014 ||
            // test name field too, because code might not be present
            // everything except Firefox
            e.name === 'QuotaExceededError' ||
            // Firefox
            e.name === 'NS_ERROR_DOM_QUOTA_REACHED') &&
            // acknowledge QuotaExceededError only if there's something already stored
            (storage && storage.length !== 0);
    }
}

Используйте этот фрагмент, как этот, и используйте, например, cookie:

if (storageAvailable('localStorage')) {
  // Yippee! We can use localStorage awesomeness
}
else {
  // Too bad, no localStorage for us
  document.cookie = key + "=" + encodeURIComponent(value) + expires + "; path=/";
}

Я сделал пакет fallbackstorage , который использует этот фрагмент для проверки доступности хранилища и возврата к реализованному вручную MemoryStorage.

import {getSafeStorage} from 'fallbackstorage'

getSafeStorage().setItem('test', '1') // always work
transang
источник
-1
var mod = 'test';
      try {
        sessionStorage.setItem(mod, mod);
        sessionStorage.removeItem(mod);
        return true;
      } catch (e) {
        return false;
      }
Наим ДОГАН
источник
1
Может быть, вы хотите добавить несколько слов объяснения?
Бог
-2

Следующий скрипт решил мою проблему:

// Fake localStorage implementation. 
// Mimics localStorage, including events. 
// It will work just like localStorage, except for the persistant storage part. 

var fakeLocalStorage = function() {
  var fakeLocalStorage = {};
  var storage; 

  // If Storage exists we modify it to write to our fakeLocalStorage object instead. 
  // If Storage does not exist we create an empty object. 
  if (window.Storage && window.localStorage) {
    storage = window.Storage.prototype; 
  } else {
    // We don't bother implementing a fake Storage object
    window.localStorage = {}; 
    storage = window.localStorage; 
  }

  // For older IE
  if (!window.location.origin) {
    window.location.origin = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ':' + window.location.port: '');
  }

  var dispatchStorageEvent = function(key, newValue) {
    var oldValue = (key == null) ? null : storage.getItem(key); // `==` to match both null and undefined
    var url = location.href.substr(location.origin.length);
    var storageEvent = document.createEvent('StorageEvent'); // For IE, http://stackoverflow.com/a/25514935/1214183

    storageEvent.initStorageEvent('storage', false, false, key, oldValue, newValue, url, null);
    window.dispatchEvent(storageEvent);
  };

  storage.key = function(i) {
    var key = Object.keys(fakeLocalStorage)[i];
    return typeof key === 'string' ? key : null;
  };

  storage.getItem = function(key) {
    return typeof fakeLocalStorage[key] === 'string' ? fakeLocalStorage[key] : null;
  };

  storage.setItem = function(key, value) {
    dispatchStorageEvent(key, value);
    fakeLocalStorage[key] = String(value);
  };

  storage.removeItem = function(key) {
    dispatchStorageEvent(key, null);
    delete fakeLocalStorage[key];
  };

  storage.clear = function() {
    dispatchStorageEvent(null, null);
    fakeLocalStorage = {};
  };
};

// Example of how to use it
if (typeof window.localStorage === 'object') {
  // Safari will throw a fit if we try to use localStorage.setItem in private browsing mode. 
  try {
    localStorage.setItem('localStorageTest', 1);
    localStorage.removeItem('localStorageTest');
  } catch (e) {
    fakeLocalStorage();
  }
} else {
  // Use fake localStorage for any browser that does not support it.
  fakeLocalStorage();
}

Он проверяет, существует ли localStorage и может ли он использоваться, а в отрицательном случае создает поддельное локальное хранилище и использует его вместо исходного localStorage. Пожалуйста, дайте мне знать, если вам нужна дополнительная информация.

Богдан Матес
источник