Offline Storage
There were several iterations of prototypes and standardized technologies for offline storage capabilities for Web applications. First attempts were either just hacky workarounds (like to store data in cookies) or required additional software (like Flash or Google Gears). Later, Web SQL idea, basically to include SQLite natively within a browser, was coined and implemented throughout some browsers, but deprecated later due to the standardization difficulties.
Right now there are at least three distinct and independent technologies standardized and available. The simplest one is Web Storage - a key-value string storage, allowing Web applications to store data either persistently and cross-window (localStorage
) or for a single session in a single browser tab (sessionStorage
). The more sophisticated IndexedDB is a low-level API over database-like structures with transactions and cursors iterating by indexes. The newest addition - Cache API is a specialized solution to keep Request
/Response
pairs, useful mostly within Service Worker implementation.
Live example and usage data shown here are referring to Web Storage engine. For details on IndexedDB, refer to caniuse.com.
The actual persistence of data stored in any of the persistent stores (be it localStorage
, IndexedDB or Cache API) is browser-managed and by default might be wiped out without end-user consent in case of memory pressure conditions. To address this problem, Storage API was introduced - it gives the Web applications a method to store the data in a fully reliable way if the user permits it to do so. Chrome's implementation grants this permission based on user-engagement-based heuristic, while Firefox asks for the permission explicitly.
API glimpse
Web Storage API
window.sessionStorage
- Gives an access to the Web Storage engine with per-session objects lifetime.
window.localStorage
- Gives an access to the Web Storage engine with persistent objects lifetime.
storage.setItem(key, value)
- Saves the
value
string under thekey
in the selected storage engine. storage.getItem(key)
- Returns the string value stored under the
key
in the selected storage engine. storage.removeItem(key)
- Removes the string value stored under the
key
from the selected storage engine. storage.clear()
- Removes all the string values stored in the selected storage engine.
window.addEventListener('storage', listener)
- An event fired when the data stored in either
sessionStorage
orlocalStorage
has been changed externally.
IndexedDB
let openRequest = window.indexedDB.open(name, version)
- Triggers opening a database connection to either existing or newly-created database. Returns an object that fires
success
event when the connection is established. let db = openRequest.result
- Gives an access to the open database connection instance - available after
success
was fired. db.createObjectStore(storeName, options)
- Creates a named container (object store) for objects in the opened database.
let tx = db.transaction(storeName)
- Opens a data-reading or data-manipulation transaction scoped to the given object store(s).
tx.objectStore.put(value, key)
- Saves the
value
in the currently opened object store. tx.objectStore.get(key)
- Gets the object stored under a
key
in the currently opened object store. tx.createIndex(name, keyPath, options)
- Creates an index that allows to seek for the stored objects using the property specified via
keyPath
. tx.index(name).get(key)
- Gets the object having the particular index
keyPath
equal to thekey
specified.
Cache API
let cache = window.caches.open(key)
- Returns a
Promise
that resolves to a store "bucket" object giving an access to the cachedResponse
objects. cache.put(request, response)
- Saves the
Response
object to the cache with its correspondingRequest
object. cache.match(request, option)
- Returns a
Promise
that resolves to theResponse
object matching the specifiedRequest
(with theoptions
-controlled level of exactness) found in the opened cache "bucket". cache.delete(request, option)
- Removes the
Response
object matching the specifiedRequest
(with theoptions
-controlled level of exactness) found in the opened cache "bucket".
Storage API (persistence permission)
navigator.storage.persist()
- Requests a permission to turn the data saved by the Web application into persistent data. Returns a
Promise
that resolves with a boolean value indicating whether the permission was granted. navigator.storage.persisted()
- Returns a
Promise
that resolves with a boolean value indicating whether the persistent storage permission was already granted.
Live Demo
Open the example in another tab and change the value there to see the synchronization via storage
event.
-
if ('localStorage' in window || 'sessionStorage' in window) { var selectedEngine; var logTarget = document.getElementById('target'); var valueInput = document.getElementById('value'); var reloadInputValue = function () { console.log(selectedEngine, window[selectedEngine].getItem('myKey')) valueInput.value = window[selectedEngine].getItem('myKey') || ''; } var selectEngine = function (engine) { selectedEngine = engine; reloadInputValue(); }; function handleChange(change) { var timeBadge = new Date().toTimeString().split(' ')[0]; var newState = document.createElement('p'); newState.innerHTML = '<span class="badge">' + timeBadge + '</span> ' + change + '.'; logTarget.appendChild(newState); } var radios = document.querySelectorAll('#selectEngine input'); for (var i = 0; i < radios.length; ++i) { radios[i].addEventListener('change', function () { selectEngine(this.value) }); } selectEngine('localStorage'); valueInput.addEventListener('keyup', function () { window[selectedEngine].setItem('myKey', this.value); }); var onStorageChanged = function (change) { var engine = change.storageArea === window.localStorage ? 'localStorage' : 'sessionStorage'; handleChange('External change in <b>' + engine + '</b>: key <b>' + change.key + '</b> changed from <b>' + change.oldValue + '</b> to <b>' + change.newValue + '</b>'); if (engine === selectedEngine) { reloadInputValue(); } } window.addEventListener('storage', onStorageChanged); }
-
<p> <label>Engine</label> </p> <div role="group" id="selectEngine"> <input type="radio" name="engine" value="localStorage" checked/> Persistent Storage <input type="radio" name="engine" value="sessionStorage"/> Per-Session Storage </div> <p> <label for="value">Value for <code>myKey</code></label> <input type="text" id="value"> </p> <p>Open the example in another tab and change the value there to see the synchronization via <code>storage</code> event.</p> <div id="target"></div>