This is an inquiry into whether it might be possible for Promises/A+ to support IndexedDB.
Naturally one answer might be, no, it's simply out of scope for Promises/A+.
On the other hand... it's looking like IndexedDB is going to be the major local store for web apps; the older Web SQL Database is deprecated (and never was implemented in Firefox), and it is impossible to use the venerable Web Storage safely from multiple windows (as Chrome doesn't implement the ill-specified storage mutex).
A promises interface to IndexedDB is much more pleasant than the native event model, so it would be nice to support it if we could.
The code below illustrates a scenario which uses four components. All components are written with the assumption that Promises/A+ is the only API available for promises (we don't use extensions such as otherwise
and so on).
- a base Promises library (e.g. when, avow, etc.)
- a library which provides a promises based interface to IndexedDB.
- an independent Promises utility library. Such a library provides useful utilities on top of promises (in this example it does tracing, something else it could do would be say an implementation similar to when.join, but built on top of Promises/A+ only). The utility library does not itself implement promises.
- a user program which uses the IndexedDB library and the utility library.
In this example the code works when the synchronous when
implementation is plugged in, but fails when the asynchronous avow
library is plugged in. With IndexedDB, a transaction is only kept alive if it is referenced at least once through the event loop, and so calling callbacks in the next event loop causes the transaction to be lost.
Note that merely leaving unspecified whether callbacks are synchronous or asynchronous doesn't help. To be able to keep the transaction alive, the callback must be synchronous. Thus while the IndexedDB library would presumably decide to be synchronous, the user could not reliably combine the IndexedDB library with the independent utility library merely on the strength of the Promises/A+ spec (if the spec left the synchronous behavior unspecified).
Now this is entirely speculative, and I haven't written any code or thought it through, but suppose callbacks were by default required to be asynchronous, but (for a particular execution extent) callbacks could be required to be synchronous. A library would either generate a synchronous or asynchronous callback depending on the mode, or, if it could only support asynchronous callbacks (xhr requests etc.) it would throw an error if it were called in the wrong mode. The user could say however for a particular callback "no, this one can be asynchronous" (because it didn't use the transaction or whatever).
Well, probably too complicated to make it into Promises/A+, but interesting to think about anyway.
require ['avow', 'when'], (avow, _when) ->
## base Promises implementation
pending = ->
deferred = _when.defer()
{
promise: deferred.promise
fulfill: deferred.resolve
reject: deferred.reject
}
# or
# pending = ->
# v = avow()
# {
# promise: v.promise
# fulfill: v.fulfill
# reject: v.reject
# }
## a promises utility library
fulfilled = (val) ->
pend = pending()
pend.fulfill(val)
pend.promise
isPromise = (x) -> x and typeof(x.then) is 'function'
toPromise = (x) ->
if isPromise(x)
x
else
pend = pending()
pend.fulfill(x)
pend.promise
trace = (name, promiseOrValue) ->
promise = toPromise(promiseOrValue)
promise.then((value) -> console.log 'trace', name, value)
promise
## a Promises based wrapper library for indexDB
indexedDB = window.indexedDB or
window.mozIndexedDB or
window.webkitIndexedDB
request_to_promise = (req) ->
pend = pending()
req.onerror = (event) -> pend.reject()
req.onsuccess = (event) -> pend.fulfill(req.result)
pend.promise
delete_database = (database_name) ->
request_to_promise(indexedDB.deleteDatabase(database_name))
open_database = (database_name, version, onUpgradeNeeded) ->
req = indexedDB.open(database_name, version)
req.onupgradeneeded = (event) ->
onUpgradeNeeded(event.target.result)
request_to_promise req
db_transaction = (db, store_names) ->
fulfilled(db.transaction(store_names))
store_get = (objectStore, key) ->
request_to_promise objectStore.get(key)
## user code
setup_database = (db) ->
objectStore = db.createObjectStore("kv", { keyPath: "k" })
objectStore.add({k: 'a', v: 1})
objectStore.add({k: 'b', v: 2})
delete_database('test')
.then(=> open_database('test', 1, setup_database))
.then((db) -> db_transaction(db, ['kv']))
.then((tx) ->
trace('transaction mode', tx.mode)
.then(-> trace('a', store_get(tx.objectStore('kv'), 'a')))
.then(-> trace('b', store_get(tx.objectStore('kv'), 'b')))
)
.then(null, (reason) =>
console.log 'error', reason, reason.message
)