UNPKG

localforage

Version:
373 lines (312 loc) 12.3 kB
// Some code originally from async_storage.js in // [Gaia](https://github.com/mozilla-b2g/gaia). (function() { 'use strict'; // Originally found in https://github.com/mozilla-b2g/gaia/blob/e8f624e4cc9ea945727278039b3bc9bcb9f8667a/shared/js/async_storage.js // Promises! var Promise = (typeof module !== 'undefined' && module.exports) ? require('promise') : this.Promise; // Initialize IndexedDB; fall back to vendor-prefixed versions if needed. var indexedDB = indexedDB || this.indexedDB || this.webkitIndexedDB || this.mozIndexedDB || this.OIndexedDB || this.msIndexedDB; // If IndexedDB isn't available, we get outta here! if (!indexedDB) { return; } // Open the IndexedDB database (automatically creates one if one didn't // previously exist), using any options set in the config. function _initStorage(options) { var self = this; var dbInfo = { db: null }; if (options) { for (var i in options) { dbInfo[i] = options[i]; } } return new Promise(function(resolve, reject) { var openreq = indexedDB.open(dbInfo.name, dbInfo.version); openreq.onerror = function() { reject(openreq.error); }; openreq.onupgradeneeded = function() { // First time setup: create an empty object store openreq.result.createObjectStore(dbInfo.storeName); }; openreq.onsuccess = function() { dbInfo.db = openreq.result; self._dbInfo = dbInfo; resolve(); }; }); } function getItem(key, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } 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; } resolve(value); }; req.onerror = function() { reject(req.error); }; }).catch(reject); }); executeDeferedCallback(promise, callback); return promise; } function setItem(key, value, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var dbInfo = self._dbInfo; var store = dbInfo.db.transaction(dbInfo.storeName, 'readwrite') .objectStore(dbInfo.storeName); // 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; } var req = store.put(value, key); req.onsuccess = 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); }; req.onerror = function() { reject(req.error); }; }).catch(reject); }); executeDeferedCallback(promise, callback); return promise; } function removeItem(key, callback) { var self = this; // Cast the key to a string, as that's all we can set as a key. if (typeof key !== 'string') { window.console.warn(key + ' used as a key, but it is not a string.'); key = String(key); } var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var dbInfo = self._dbInfo; var store = dbInfo.db.transaction(dbInfo.storeName, 'readwrite') .objectStore(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); req.onsuccess = function() { resolve(); }; req.onerror = function() { reject(req.error); }; // The request will be aborted if we've exceeded our storage // space. In this case, we will reject with a specific // "QuotaExceededError". req.onabort = function(event) { var error = event.target.error; if (error === 'QuotaExceededError') { reject(error); } }; }).catch(reject); }); executeDeferedCallback(promise, callback); return promise; } function clear(callback) { var self = this; var promise = new Promise(function(resolve, reject) { self.ready().then(function() { var dbInfo = self._dbInfo; var store = dbInfo.db.transaction(dbInfo.storeName, 'readwrite') .objectStore(dbInfo.storeName); var req = store.clear(); req.onsuccess = function() { resolve(); }; req.onerror = function() { reject(req.error); }; }).catch(reject); }); executeDeferedCallback(promise, callback); return promise; } function length(callback) { var self = this; 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(); req.onsuccess = function() { resolve(req.result); }; req.onerror = function() { reject(req.error); }; }).catch(reject); }); executeCallback(promise, callback); return promise; } function key(n, callback) { var self = this; var promise = new Promise(function(resolve, reject) { if (n < 0) { resolve(null); return; } 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; } 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(reject); }); executeCallback(promise, callback); return promise; } function keys(callback) { var self = this; 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; } keys.push(cursor.key); cursor.continue(); }; req.onerror = function() { reject(req.error); }; }).catch(reject); }); executeCallback(promise, callback); return promise; } function executeCallback(promise, callback) { if (callback) { promise.then(function(result) { callback(null, result); }, function(error) { callback(error); }); } } function executeDeferedCallback(promise, callback) { if (callback) { promise.then(function(result) { deferCallback(callback, result); }, function(error) { callback(error); }); } } // Under Chrome the callback is called before the changes (save, clear) // are actually made. So we use a defer function which wait that the // call stack to be empty. // For more info : https://github.com/mozilla/localForage/issues/175 // Pull request : https://github.com/mozilla/localForage/pull/178 function deferCallback(callback, result) { if (callback) { return setTimeout(function() { return callback(null, result); }, 0); } } var asyncStorage = { _driver: 'asyncStorage', _initStorage: _initStorage, getItem: getItem, setItem: setItem, removeItem: removeItem, clear: clear, length: length, key: key, keys: keys }; if (typeof define === 'function' && define.amd) { define('asyncStorage', function() { return asyncStorage; }); } else if (typeof module !== 'undefined' && module.exports) { module.exports = asyncStorage; } else { this.asyncStorage = asyncStorage; } }).call(window);