async-storage.js (7235B)
1 /* This Source Code Form is subject to the terms of the Mozilla Public 2 * License, v. 2.0. If a copy of the MPL was not distributed with this 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 4 5 /** 6 * 7 * Adapted from https://github.com/mozilla-b2g/gaia/blob/f09993563fb5fec4393eb71816ce76cb00463190/shared/js/async_storage.js 8 * (converted to use Promises instead of callbacks). 9 * 10 * This file defines an asynchronous version of the localStorage API, backed by 11 * an IndexedDB database. It creates a global asyncStorage object that has 12 * methods like the localStorage object. 13 * 14 * To store a value use setItem: 15 * 16 * asyncStorage.setItem("key", "value"); 17 * 18 * This returns a promise in case you want confirmation that the value has been stored. 19 * 20 * asyncStorage.setItem("key", "newvalue").then(function() { 21 * console.log("new value stored"); 22 * }); 23 * 24 * To read a value, call getItem(), but note that you must wait for a promise 25 * resolution for the value to be retrieved. 26 * 27 * asyncStorage.getItem("key").then(function(value) { 28 * console.log("The value of key is:", value); 29 * }); 30 * 31 * Note that unlike localStorage, asyncStorage does not allow you to store and 32 * retrieve values by setting and querying properties directly. You cannot just 33 * write asyncStorage.key; you have to explicitly call setItem() or getItem(). 34 * 35 * removeItem(), clear(), length(), and key() are like the same-named methods of 36 * localStorage, and all return a promise. 37 * 38 * The asynchronous nature of getItem() makes it tricky to retrieve multiple 39 * values. But unlike localStorage, asyncStorage does not require the values you 40 * store to be strings. So if you need to save multiple values and want to 41 * retrieve them together, in a single asynchronous operation, just group the 42 * values into a single object. The properties of this object may not include 43 * DOM elements, but they may include things like Blobs and typed arrays. 44 * 45 */ 46 47 "use strict"; 48 49 const DBNAME = "devtools-async-storage"; 50 const DBVERSION = 1; 51 const STORENAME = "keyvaluepairs"; 52 var db = null; 53 54 loader.lazyRequireGetter( 55 this, 56 "indexedDB", 57 "resource://devtools/shared/indexed-db.js" 58 ); 59 60 function withStore(type, onsuccess, onerror) { 61 if (db) { 62 const transaction = db.transaction(STORENAME, type); 63 const store = transaction.objectStore(STORENAME); 64 onsuccess(store); 65 } else { 66 const openreq = indexedDB.open(DBNAME, DBVERSION); 67 openreq.onerror = function withStoreOnError() { 68 onerror(); 69 }; 70 openreq.onupgradeneeded = function withStoreOnUpgradeNeeded() { 71 // First time setup: create an empty object store 72 openreq.result.createObjectStore(STORENAME); 73 }; 74 openreq.onsuccess = function withStoreOnSuccess() { 75 db = openreq.result; 76 const transaction = db.transaction(STORENAME, type); 77 const store = transaction.objectStore(STORENAME); 78 onsuccess(store); 79 }; 80 } 81 } 82 83 function getItem(itemKey) { 84 return new Promise((resolve, reject) => { 85 let req; 86 withStore( 87 "readonly", 88 store => { 89 store.transaction.oncomplete = function onComplete() { 90 let value = req.result; 91 if (value === undefined) { 92 value = null; 93 } 94 resolve(value); 95 }; 96 req = store.get(itemKey); 97 req.onerror = function getItemOnError() { 98 console.error("Error in asyncStorage.getItem():", req.error.name); 99 reject( 100 new Error( 101 `async-storage failed to get item for key: ${itemKey}, error: ${req.error}` 102 ) 103 ); 104 }; 105 }, 106 reject 107 ); 108 }); 109 } 110 111 function setItem(itemKey, value) { 112 return new Promise((resolve, reject) => { 113 withStore( 114 "readwrite", 115 store => { 116 store.transaction.oncomplete = resolve; 117 const req = store.put(value, itemKey); 118 req.onerror = function setItemOnError() { 119 console.error("Error in asyncStorage.setItem():", req.error.name); 120 reject( 121 new Error( 122 `async-storage failed to set item for key: ${itemKey}, error: ${req.error}` 123 ) 124 ); 125 }; 126 }, 127 reject 128 ); 129 }); 130 } 131 132 function removeItem(itemKey) { 133 return new Promise((resolve, reject) => { 134 withStore( 135 "readwrite", 136 store => { 137 store.transaction.oncomplete = resolve; 138 const req = store.delete(itemKey); 139 req.onerror = function removeItemOnError() { 140 console.error("Error in asyncStorage.removeItem():", req.error.name); 141 reject( 142 new Error( 143 `async-storage failed to remove item for key: ${itemKey}, error: ${req.error}` 144 ) 145 ); 146 }; 147 }, 148 reject 149 ); 150 }); 151 } 152 153 function clear() { 154 return new Promise((resolve, reject) => { 155 withStore( 156 "readwrite", 157 store => { 158 store.transaction.oncomplete = resolve; 159 const req = store.clear(); 160 req.onerror = function clearOnError() { 161 console.error("Error in asyncStorage.clear():", req.error.name); 162 reject( 163 new Error(`async-storage failed to clear, error: ${req.error}`) 164 ); 165 }; 166 }, 167 reject 168 ); 169 }); 170 } 171 172 function length() { 173 return new Promise((resolve, reject) => { 174 let req; 175 withStore( 176 "readonly", 177 store => { 178 store.transaction.oncomplete = function onComplete() { 179 resolve(req.result); 180 }; 181 req = store.count(); 182 req.onerror = function lengthOnError() { 183 console.error("Error in asyncStorage.length():", req.error.name); 184 reject( 185 new Error( 186 `async-storage failed to retrieve length, error: ${req.error}` 187 ) 188 ); 189 }; 190 }, 191 reject 192 ); 193 }); 194 } 195 196 function key(n) { 197 return new Promise((resolve, reject) => { 198 if (n < 0) { 199 resolve(null); 200 return; 201 } 202 203 let req; 204 withStore( 205 "readonly", 206 store => { 207 store.transaction.oncomplete = function onComplete() { 208 const cursor = req.result; 209 resolve(cursor ? cursor.key : null); 210 }; 211 let advanced = false; 212 req = store.openCursor(); 213 req.onsuccess = function keyOnSuccess() { 214 const cursor = req.result; 215 if (!cursor) { 216 // this means there weren"t enough keys 217 return; 218 } 219 if (n === 0 || advanced) { 220 // Either 1) we have the first key, return it if that's what they 221 // wanted, or 2) we"ve got the nth key. 222 return; 223 } 224 225 // Otherwise, ask the cursor to skip ahead n records 226 advanced = true; 227 cursor.advance(n); 228 }; 229 req.onerror = function keyOnError() { 230 console.error("Error in asyncStorage.key():", req.error.name); 231 reject( 232 new Error( 233 `async-storage failed to retrieve key with n: ${n}, error: ${req.error}` 234 ) 235 ); 236 }; 237 }, 238 reject 239 ); 240 }); 241 } 242 243 exports.getItem = getItem; 244 exports.setItem = setItem; 245 exports.removeItem = removeItem; 246 exports.clear = clear; 247 exports.length = length; 248 exports.key = key;