mirror of
https://github.com/localForage/localForage.git
synced 2026-02-01 15:32:04 +00:00
Merge pull request #694 from thgreasi/idb-reconnect-tests-rebase
feat(indexeddb): try reconnect to the DB after InvalidStateError
This commit is contained in:
commit
5e93acf5a9
454
dist/localforage.js
vendored
454
dist/localforage.js
vendored
@ -470,6 +470,10 @@ var supportsBlobs;
|
||||
var dbContexts;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
// Transaction Modes
|
||||
var READ_ONLY = 'readonly';
|
||||
var READ_WRITE = 'readwrite';
|
||||
|
||||
// Transform a binary string to an array buffer, because otherwise
|
||||
// weird stuff happens when you try to work with the binary string directly.
|
||||
// It is known.
|
||||
@ -502,7 +506,7 @@ function _binStringToArrayBuffer(bin) {
|
||||
//
|
||||
function _checkBlobSupportWithoutCaching(idb) {
|
||||
return new Promise$1(function (resolve) {
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite');
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, READ_WRITE);
|
||||
var blob = createBlob(['']);
|
||||
txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
|
||||
|
||||
@ -572,6 +576,19 @@ function _advanceReadiness(dbInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
function _rejectReadiness(dbInfo, err) {
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
|
||||
// Dequeue a deferred operation.
|
||||
var deferredOperation = dbContext.deferredOperations.pop();
|
||||
|
||||
// Reject its promise (which is part of the database readiness
|
||||
// chain of promises).
|
||||
if (deferredOperation) {
|
||||
deferredOperation.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
function _getConnection(dbInfo, upgradeNeeded) {
|
||||
return new Promise$1(function (resolve, reject) {
|
||||
|
||||
@ -714,6 +731,51 @@ function _fullyReady(callback) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Try to establish a new db connection to replace the
|
||||
// current one which is broken (i.e. experiencing
|
||||
// InvalidStateError while creating a transaction).
|
||||
function _tryReconnect(dbInfo) {
|
||||
_deferReadiness(dbInfo);
|
||||
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
var forages = dbContext.forages;
|
||||
|
||||
for (var i = 0; i < forages.length; i++) {
|
||||
if (forages[i]._dbInfo.db) {
|
||||
forages[i]._dbInfo.db.close();
|
||||
forages[i]._dbInfo.db = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _getConnection(dbInfo, false).then(function (db) {
|
||||
for (var j = 0; j < forages.length; j++) {
|
||||
forages[j]._dbInfo.db = db;
|
||||
}
|
||||
})["catch"](function (err) {
|
||||
_rejectReadiness(dbInfo, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
// FF doesn't like Promises (micro-tasks) and IDDB store operations,
|
||||
// so we have to do it with callbacks
|
||||
function createTransaction(dbInfo, mode, callback) {
|
||||
try {
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
} catch (err) {
|
||||
if (!dbInfo.db || err.name === 'InvalidStateError') {
|
||||
return _tryReconnect(dbInfo).then(function () {
|
||||
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
});
|
||||
}
|
||||
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Open the IndexedDB database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
@ -820,24 +882,33 @@ function getItem(key, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function () {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function () {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -851,35 +922,46 @@ function iterate(iterator, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key, iterationNumber++);
|
||||
|
||||
if (result !== void 0) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor["continue"]();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key, iterationNumber++);
|
||||
|
||||
// when the iterator callback retuns any
|
||||
// (non-`undefined`) value, then we stop
|
||||
// the iteration immediately
|
||||
if (result !== void 0) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor["continue"]();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -911,35 +993,44 @@ function setItem(key, value, callback) {
|
||||
}
|
||||
return value;
|
||||
}).then(function (value) {
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -958,30 +1049,37 @@ function removeItem(key, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store["delete"](key);
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store["delete"](key);
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
transaction.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -994,19 +1092,27 @@ function clear(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -1019,17 +1125,26 @@ function length(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
var req = store.count();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
req.onsuccess = function () {
|
||||
resolve(req.result);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.count();
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -1048,40 +1163,49 @@ function key(n, callback) {
|
||||
}
|
||||
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -1094,27 +1218,35 @@ function keys(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor["continue"]();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor["continue"]();
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
|
||||
2
dist/localforage.min.js
vendored
2
dist/localforage.min.js
vendored
File diff suppressed because one or more lines are too long
454
dist/localforage.nopromises.js
vendored
454
dist/localforage.nopromises.js
vendored
@ -134,6 +134,10 @@ var supportsBlobs;
|
||||
var dbContexts;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
// Transaction Modes
|
||||
var READ_ONLY = 'readonly';
|
||||
var READ_WRITE = 'readwrite';
|
||||
|
||||
// Transform a binary string to an array buffer, because otherwise
|
||||
// weird stuff happens when you try to work with the binary string directly.
|
||||
// It is known.
|
||||
@ -166,7 +170,7 @@ function _binStringToArrayBuffer(bin) {
|
||||
//
|
||||
function _checkBlobSupportWithoutCaching(idb) {
|
||||
return new Promise$1(function (resolve) {
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite');
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, READ_WRITE);
|
||||
var blob = createBlob(['']);
|
||||
txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
|
||||
|
||||
@ -236,6 +240,19 @@ function _advanceReadiness(dbInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
function _rejectReadiness(dbInfo, err) {
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
|
||||
// Dequeue a deferred operation.
|
||||
var deferredOperation = dbContext.deferredOperations.pop();
|
||||
|
||||
// Reject its promise (which is part of the database readiness
|
||||
// chain of promises).
|
||||
if (deferredOperation) {
|
||||
deferredOperation.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
function _getConnection(dbInfo, upgradeNeeded) {
|
||||
return new Promise$1(function (resolve, reject) {
|
||||
|
||||
@ -378,6 +395,51 @@ function _fullyReady(callback) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Try to establish a new db connection to replace the
|
||||
// current one which is broken (i.e. experiencing
|
||||
// InvalidStateError while creating a transaction).
|
||||
function _tryReconnect(dbInfo) {
|
||||
_deferReadiness(dbInfo);
|
||||
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
var forages = dbContext.forages;
|
||||
|
||||
for (var i = 0; i < forages.length; i++) {
|
||||
if (forages[i]._dbInfo.db) {
|
||||
forages[i]._dbInfo.db.close();
|
||||
forages[i]._dbInfo.db = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _getConnection(dbInfo, false).then(function (db) {
|
||||
for (var j = 0; j < forages.length; j++) {
|
||||
forages[j]._dbInfo.db = db;
|
||||
}
|
||||
})["catch"](function (err) {
|
||||
_rejectReadiness(dbInfo, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
// FF doesn't like Promises (micro-tasks) and IDDB store operations,
|
||||
// so we have to do it with callbacks
|
||||
function createTransaction(dbInfo, mode, callback) {
|
||||
try {
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
} catch (err) {
|
||||
if (!dbInfo.db || err.name === 'InvalidStateError') {
|
||||
return _tryReconnect(dbInfo).then(function () {
|
||||
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
});
|
||||
}
|
||||
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Open the IndexedDB database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
@ -484,24 +546,33 @@ function getItem(key, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function () {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function () {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -515,35 +586,46 @@ function iterate(iterator, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key, iterationNumber++);
|
||||
|
||||
if (result !== void 0) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor["continue"]();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key, iterationNumber++);
|
||||
|
||||
// when the iterator callback retuns any
|
||||
// (non-`undefined`) value, then we stop
|
||||
// the iteration immediately
|
||||
if (result !== void 0) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor["continue"]();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -575,35 +657,44 @@ function setItem(key, value, callback) {
|
||||
}
|
||||
return value;
|
||||
}).then(function (value) {
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -622,30 +713,37 @@ function removeItem(key, callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store["delete"](key);
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store["delete"](key);
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
transaction.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -658,19 +756,27 @@ function clear(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
createTransaction(self._dbInfo, READ_WRITE, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
transaction.oncomplete = function () {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onabort = transaction.onerror = function () {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -683,17 +789,26 @@ function length(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
var req = store.count();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
req.onsuccess = function () {
|
||||
resolve(req.result);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.count();
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -712,40 +827,49 @@ function key(n, callback) {
|
||||
}
|
||||
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
@ -758,27 +882,35 @@ function keys(callback) {
|
||||
|
||||
var promise = new Promise$1(function (resolve, reject) {
|
||||
self.ready().then(function () {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly').objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function (err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor["continue"]();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function () {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor["continue"]();
|
||||
};
|
||||
|
||||
req.onerror = function () {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
})["catch"](reject);
|
||||
});
|
||||
|
||||
|
||||
2
dist/localforage.nopromises.min.js
vendored
2
dist/localforage.nopromises.min.js
vendored
File diff suppressed because one or more lines are too long
37
examples/indexeddb-invalidstate.html
Normal file
37
examples/indexeddb-invalidstate.html
Normal file
@ -0,0 +1,37 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf8" />
|
||||
<title>Simple localForage example</title>
|
||||
</head>
|
||||
<body>
|
||||
<script src="../dist/localforage.js"></script>
|
||||
<script>
|
||||
// Forcing IndexedDB here.
|
||||
localforage.setDriver(localforage.INDEXEDDB).then(function() {
|
||||
var key = 'STORE_KEY';
|
||||
var value = new Uint8Array(8);
|
||||
value[0] = 65
|
||||
var UNKNOWN_KEY = 'unknown_key';
|
||||
|
||||
localforage.setItem(key, value, function() {
|
||||
console.log('Saved: ' + value);
|
||||
|
||||
// causes InvalidState erros
|
||||
localforage._dbInfo.db.close();
|
||||
|
||||
localforage.getItem(key).then(function(readValue) {
|
||||
console.log('Read: ', readValue);
|
||||
}).catch(function(err) {
|
||||
console.error('Read: ', err);
|
||||
});
|
||||
|
||||
// Since this key hasn't been set yet, we'll get a null value
|
||||
localforage.getItem(UNKNOWN_KEY).then(function(err, readValue) {
|
||||
console.log('Result of reading ' + UNKNOWN_KEY, readValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -12,6 +12,10 @@ var supportsBlobs;
|
||||
var dbContexts;
|
||||
var toString = Object.prototype.toString;
|
||||
|
||||
// Transaction Modes
|
||||
var READ_ONLY = 'readonly';
|
||||
var READ_WRITE = 'readwrite';
|
||||
|
||||
// Transform a binary string to an array buffer, because otherwise
|
||||
// weird stuff happens when you try to work with the binary string directly.
|
||||
// It is known.
|
||||
@ -44,7 +48,7 @@ function _binStringToArrayBuffer(bin) {
|
||||
//
|
||||
function _checkBlobSupportWithoutCaching(idb) {
|
||||
return new Promise(function(resolve) {
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, 'readwrite');
|
||||
var txn = idb.transaction(DETECT_BLOB_SUPPORT_STORE, READ_WRITE);
|
||||
var blob = createBlob(['']);
|
||||
txn.objectStore(DETECT_BLOB_SUPPORT_STORE).put(blob, 'key');
|
||||
|
||||
@ -115,6 +119,19 @@ function _advanceReadiness(dbInfo) {
|
||||
}
|
||||
}
|
||||
|
||||
function _rejectReadiness(dbInfo, err) {
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
|
||||
// Dequeue a deferred operation.
|
||||
var deferredOperation = dbContext.deferredOperations.pop();
|
||||
|
||||
// Reject its promise (which is part of the database readiness
|
||||
// chain of promises).
|
||||
if (deferredOperation) {
|
||||
deferredOperation.reject(err);
|
||||
}
|
||||
}
|
||||
|
||||
function _getConnection(dbInfo, upgradeNeeded) {
|
||||
return new Promise(function(resolve, reject) {
|
||||
|
||||
@ -262,6 +279,54 @@ function _fullyReady(callback) {
|
||||
return promise;
|
||||
}
|
||||
|
||||
// Try to establish a new db connection to replace the
|
||||
// current one which is broken (i.e. experiencing
|
||||
// InvalidStateError while creating a transaction).
|
||||
function _tryReconnect(dbInfo) {
|
||||
_deferReadiness(dbInfo);
|
||||
|
||||
var dbContext = dbContexts[dbInfo.name];
|
||||
var forages = dbContext.forages;
|
||||
|
||||
for (var i = 0; i < forages.length; i++) {
|
||||
if (forages[i]._dbInfo.db) {
|
||||
forages[i]._dbInfo.db.close();
|
||||
forages[i]._dbInfo.db = null;
|
||||
}
|
||||
}
|
||||
|
||||
return _getConnection(dbInfo, false).then(function(db) {
|
||||
for (var j = 0; j < forages.length; j++) {
|
||||
forages[j]._dbInfo.db = db;
|
||||
}
|
||||
}).catch(function(err) {
|
||||
_rejectReadiness(dbInfo, err);
|
||||
throw err;
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// FF doesn't like Promises (micro-tasks) and IDDB store operations,
|
||||
// so we have to do it with callbacks
|
||||
function createTransaction(dbInfo, mode, callback) {
|
||||
try {
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
} catch (err) {
|
||||
if (!dbInfo.db ||
|
||||
err.name === 'InvalidStateError') {
|
||||
return _tryReconnect(dbInfo).then(function() {
|
||||
|
||||
var tx = dbInfo.db.transaction(dbInfo.storeName, mode);
|
||||
callback(null, tx);
|
||||
});
|
||||
}
|
||||
|
||||
callback(err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Open the IndexedDB database (automatically creates one if one didn't
|
||||
// previously exist), using any options set in the config.
|
||||
function _initStorage(options) {
|
||||
@ -355,6 +420,7 @@ function _initStorage(options) {
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
function getItem(key, callback) {
|
||||
var self = this;
|
||||
|
||||
@ -367,25 +433,33 @@ function getItem(key, callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.get(key);
|
||||
|
||||
req.onsuccess = function() {
|
||||
var value = req.result;
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
resolve(value);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -399,37 +473,47 @@ function iterate(iterator, callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key,
|
||||
iterationNumber++);
|
||||
|
||||
if (result !== void(0)) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor.continue();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var iterationNumber = 1;
|
||||
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (cursor) {
|
||||
var value = cursor.value;
|
||||
if (_isEncodedBlob(value)) {
|
||||
value = _decodeBlob(value);
|
||||
}
|
||||
var result = iterator(value, cursor.key,
|
||||
iterationNumber++);
|
||||
|
||||
// when the iterator callback retuns any
|
||||
// (non-`undefined`) value, then we stop
|
||||
// the iteration immediately
|
||||
if (result !== void(0)) {
|
||||
resolve(result);
|
||||
} else {
|
||||
cursor.continue();
|
||||
}
|
||||
} else {
|
||||
resolve();
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -462,35 +546,44 @@ function setItem(key, value, callback) {
|
||||
}
|
||||
return value;
|
||||
}).then(function(value) {
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function() {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.put(value, key);
|
||||
|
||||
// The reason we don't _save_ null is because IE 10 does
|
||||
// not support saving the `null` type in IndexedDB. How
|
||||
// ironic, given the bug below!
|
||||
// See: https://github.com/mozilla/localForage/issues/161
|
||||
if (value === null) {
|
||||
value = undefined;
|
||||
}
|
||||
|
||||
transaction.oncomplete = function() {
|
||||
// Cast to undefined so the value passed to
|
||||
// callback/promise is the same as what one would get out
|
||||
// of `getItem()` later. This leads to some weirdness
|
||||
// (setItem('foo', undefined) will return `null`), but
|
||||
// it's not my fault localStorage is our baseline and that
|
||||
// it's weird.
|
||||
if (value === undefined) {
|
||||
value = null;
|
||||
}
|
||||
|
||||
resolve(value);
|
||||
};
|
||||
transaction.onabort = transaction.onerror = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -510,30 +603,37 @@ function removeItem(key, callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store.delete(key);
|
||||
transaction.oncomplete = function() {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
// We use a Grunt task to make this safe for IE and some
|
||||
// versions of Android (including those used by Cordova).
|
||||
// Normally IE won't like `.delete()` and will insist on
|
||||
// using `['delete']()`, but we have a build step that
|
||||
// fixes this for us now.
|
||||
var req = store.delete(key);
|
||||
transaction.oncomplete = function() {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
transaction.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
// The request will be also be aborted if we've exceeded our storage
|
||||
// space.
|
||||
transaction.onabort = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -546,19 +646,27 @@ function clear(callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var transaction = dbInfo.db.transaction(dbInfo.storeName, 'readwrite');
|
||||
var store = transaction.objectStore(dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
createTransaction(self._dbInfo, READ_WRITE, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
transaction.oncomplete = function() {
|
||||
resolve();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.clear();
|
||||
|
||||
transaction.onabort = transaction.onerror = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
transaction.oncomplete = function() {
|
||||
resolve();
|
||||
};
|
||||
|
||||
transaction.onabort = transaction.onerror = function() {
|
||||
var err = req.error ? req.error : req.transaction.error;
|
||||
reject(err);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -571,18 +679,26 @@ function length(callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
var req = store.count();
|
||||
createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
req.onsuccess = function() {
|
||||
resolve(req.result);
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.count();
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function() {
|
||||
resolve(req.result);
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -601,41 +717,49 @@ function key(n, callback) {
|
||||
}
|
||||
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var advanced = false;
|
||||
var req = store.openCursor();
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
if (!cursor) {
|
||||
// this means there weren't enough keys
|
||||
resolve(null);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (n === 0) {
|
||||
// We have the first key, return it if that's what they
|
||||
// wanted.
|
||||
resolve(cursor.key);
|
||||
} else {
|
||||
if (!advanced) {
|
||||
// Otherwise, ask the cursor to skip ahead n
|
||||
// records.
|
||||
advanced = true;
|
||||
cursor.advance(n);
|
||||
} else {
|
||||
// When we get here, we've got the nth key.
|
||||
resolve(cursor.key);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
@ -648,28 +772,35 @@ function keys(callback) {
|
||||
|
||||
var promise = new Promise(function(resolve, reject) {
|
||||
self.ready().then(function() {
|
||||
var dbInfo = self._dbInfo;
|
||||
var store = dbInfo.db.transaction(dbInfo.storeName, 'readonly')
|
||||
.objectStore(dbInfo.storeName);
|
||||
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
createTransaction(self._dbInfo, READ_ONLY, function(err, transaction) {
|
||||
if (err) {
|
||||
return reject(err);
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor.continue();
|
||||
};
|
||||
try {
|
||||
var store = transaction.objectStore(self._dbInfo.storeName);
|
||||
var req = store.openCursor();
|
||||
var keys = [];
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
req.onsuccess = function() {
|
||||
var cursor = req.result;
|
||||
|
||||
if (!cursor) {
|
||||
resolve(keys);
|
||||
return;
|
||||
}
|
||||
|
||||
keys.push(cursor.key);
|
||||
cursor.continue();
|
||||
};
|
||||
|
||||
req.onerror = function() {
|
||||
reject(req.error);
|
||||
};
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
});
|
||||
}).catch(reject);
|
||||
});
|
||||
|
||||
|
||||
@ -264,6 +264,83 @@ DRIVERS.forEach(function(driverName) {
|
||||
localforage._dbInfo.db.transaction = transaction;
|
||||
});
|
||||
});
|
||||
|
||||
describe('recover (reconnect) from IDBDatabase InvalidStateError', function() {
|
||||
|
||||
beforeEach(function(done) {
|
||||
Promise.all([
|
||||
localforage.setItem('key', 'value1'),
|
||||
localforage.setItem('key1', 'value1'),
|
||||
localforage.setItem('key2', 'value2'),
|
||||
localforage.setItem('key3', 'value3')
|
||||
]).then(function() {
|
||||
localforage._dbInfo.db.close();
|
||||
done();
|
||||
}, function(error) {
|
||||
done(error || 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves an item from the storage', function(done) {
|
||||
localforage.getItem('key').then(function(value) {
|
||||
expect(value).to.be('value1');
|
||||
done();
|
||||
}, function(error) {
|
||||
done(error || 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('retrieves more than one items from the storage', function(done) {
|
||||
Promise.all([
|
||||
localforage.getItem('key1'),
|
||||
localforage.getItem('key2'),
|
||||
localforage.getItem('key3')
|
||||
]).then(function(values) {
|
||||
expect(values).to.eql([
|
||||
'value1',
|
||||
'value2',
|
||||
'value3'
|
||||
]);
|
||||
done();
|
||||
}, function(error) {
|
||||
done(error || 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('stores and retrieves an item from the storage', function(done) {
|
||||
localforage.setItem('key', 'value1b').then(function() {
|
||||
return localforage.getItem('key');
|
||||
}).then(function(value) {
|
||||
expect(value).to.be('value1b');
|
||||
done();
|
||||
}, function(error) {
|
||||
done(error || 'error');
|
||||
});
|
||||
});
|
||||
|
||||
it('stores and retrieves more than one items from the storage', function(done) {
|
||||
Promise.all([
|
||||
localforage.setItem('key1', 'value1b'),
|
||||
localforage.setItem('key2', 'value2b'),
|
||||
localforage.setItem('key3', 'value3b')
|
||||
]).then(function() {
|
||||
return Promise.all([
|
||||
localforage.getItem('key1'),
|
||||
localforage.getItem('key2'),
|
||||
localforage.getItem('key3')
|
||||
]);
|
||||
}).then(function(values) {
|
||||
expect(values).to.eql([
|
||||
'value1b',
|
||||
'value2b',
|
||||
'value3b'
|
||||
]);
|
||||
done();
|
||||
}, function(error) {
|
||||
done(error || 'error');
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
if (driverName === localforage.WEBSQL) {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user