Safari 中的 html5 localStorage 错误: “ QUOTA_EXCEEDED_ERR: DOM Exception 22: 尝试向存储添加超过配额的内容。”

我的网络应用程序在 ios Safari 私人浏览器中有 javascript 错误:

JavaScript: 错误

未定义

异常22: 尝试向存储器添加某些内容..。

我的代码:

localStorage.setItem('test',1)
156142 次浏览

显然这是故意的。当 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;
}
}

在上面的链接上贴出的补丁对我来说不起作用,但是它起作用了:

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

在我的上下文中,刚刚开发了一个类抽象。 当我的应用程序启动时,我通过调用 GetStorage ()来检查 localStorage 是否正常工作。这个函数也返回:

  • 如果 localStorage 正常工作,则选择 localStorage
  • 或自定义类 LocalStorageAlternative 本地存储备选方案的实现

在我的代码中,我从来不直接调用 localStorage,而是调用 没关系global var,我通过调用 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();

我在使用离子框架(Angular + Cordova)时遇到了同样的问题。我知道这不能解决问题,但这是基于上述答案的角度应用程序的代码。您将拥有一个针对 iOS 版 Safari 上 localStorage 的临时解决方案。

密码如下:

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

祝你编程愉快!

正如在其他答案中提到的,当调用 localStorage.setItem(或 sessionStorage.setItem)时,iOS 和 OS X 上的 Safari 私有浏览器模式总是会出现 QuotaExceededError 错误。

一种解决方案是在使用 setItem的每个实例中执行 try/catch 或 现代化检查

然而,如果你想要一个简单的全局的垫片来阻止这个错误被抛出,以防止你的 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.');
}
}

下面是 AngularJS 的一个解决方案,它使用 生命并利用 服务都是单身

这会导致在首次注入服务时立即设置 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');
}
};
}
]);

为了扩展其他人的答案,这里有一个紧凑的解决方案,它不公开/添加任何新变量。它不能涵盖所有的基础,但它应该适合大多数人,只是希望一个页面应用程序保持功能(尽管没有数据持久性后重新加载)。

(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 = {};
};
}
})();

下面的脚本解决了我的问题:

// 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。 如果你需要进一步的信息,请告诉我。

如果不支持,就不要使用它,要检查支持,只需调用这个函数

在 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);

这将确保在所有浏览器上正确设置和检索密钥。

我创建这个 回购是为了为不支持或禁用的浏览器提供 sessionStoragelocalStorage特性。

支持浏览器

  • IE5 +
  • Chrome 所有版本
  • Mozilla 所有版本
  • Yandex 所有版本

它是如何工作的

它检测具有存储类型的特性。

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

如果支持或创建 Cookie 存储,则将 StorageService.localStorage设置为 window.localStorage。 如果支持 StorageService.sessionStorage,则将其设置为 window.sessionStorage,或为 SPA 创建内存存储,为非 SPA 创建具有会话特性的 cookie 存储。

似乎 Safari 11改变了这种行为,现在本地存储可以在私有浏览器窗口中工作!

我们的网络应用程序曾经在 Safari 私人浏览器中失败,现在它完美地工作了。它在 Chrome 的私人浏览模式下一直运行良好,这种模式总是允许写入本地存储。

这在苹果2017年5月发布的第29版 Safari 技术预览版说明WebKit 发布说明中有所记录。

具体来说:

  • 修正了在私有浏览模式或 WebDriver 会话中保存到 localStorage 时的 QuotaExceededError-R215315
var mod = 'test';
try {
sessionStorage.setItem(mod, mod);
sessionStorage.removeItem(mod);
return true;
} catch (e) {
return false;
}

这里有一个 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);
}


}

我已经为这个问题创建了一个补丁。我只是在检查浏览器是否支持 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')

在某些情况下,公认的答案似乎是不够的。

为了检查是否支持 localStoragesessionStorage,我使用了来自 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=/";
}

我已经制作了 备用存储器包,它使用这个代码片段来检查存储可用性,并回退到手动实现的 memyStorage。

import {getSafeStorage} from 'fallbackstorage'


getSafeStorage().setItem('test', '1') // always work