W3C home > Mailing lists > Public > public-webapps@w3.org > January to March 2011

Re: [IndexedDB] Events and requests

From: Axel Rauschmayer <axel@rauschma.de>
Date: Tue, 11 Jan 2011 10:31:40 +0100
Message-Id: <BB91F763-44B2-413C-A07E-6E8D4ABA9620@rauschma.de>
Cc: Jonas Sicking <jonas@sicking.cc>, ben turner <bent.mozilla@gmail.com>, Jeremy Orlow <jorlow@chromium.org>, Webapps WG <public-webapps@w3.org>
To: Keean Schupke <keean@fry-it.com>
Looks great, I just tried to stay as close to the current API as possible.

A single handler should definitely be enough. Can, say, a cursor be read multiple times (if there are several success handlers)? Doesn’t that make things more complicated?

On Jan 11, 2011, at 10:22 , Keean Schupke wrote:

> Comments inline:
> 
> On 11 January 2011 07:11, Axel Rauschmayer <axel@rauschma.de> wrote:
> Coming back to the initial message in this thread (at the very bottom):
> => General rule of thumb: clearly separate input data and output data.
> 
> Using JavaScript dynamic nature, things could look as follows:
> 
> indexedDB.open('AddressBook', 'Address Book', {
>     success: function(evt) {
>     },
>     error: function(evt) {
>     }
> });
> 
> Personally I prefer a single callback passed an object.
> 
> indexedDB.open('AddressBook', 'Address Book', function(event) {
>     switch(event.status) {
>         case EVENT_SUCCESS: ....
>             break;
>         case EVENT_ERROR: ....
>             break;
>     }
> }); 
>  
> As it allows callbacks to be composed more easily.
> 
> - The last argument is thus the request and clearly input.
> 
> - If multiple success handlers are needed, success could be an array of functions (same for error handlers).
> 
> multiple handlers can be passes using a composition function:
> 
> // can be defined in the library
> var all = function(flist) {
>    return function(event) {
>        for (int i = 0; i < flist.length; i++) {
>            flist[i](event);
>        }
>     };
> };
> 
> indexedDB.open('AddressBook', 'Address Book', all([fn1, fn2, fn3]));
> 
> 
> Cheers,
> Keean.
> 
> 
> 
> - I would eliminiate readyState and move abort() to IDBEvent (=output and an interface to the DB client).
> 
> - With subclasses of IDBEvent one has the choice of eliminating them by making their fields additional parameters of success() and error(). event.result is a prime candidate for this!
> 
> - This above way eliminates the need of manipulating the request *after* (a reference to) it has been placed in the event queue.
> 
> Questions:
> 
> - Is it really necessary to make IDBEvent a subclass of Event and thus drag the DOM (which seems to be universally hated) into IndexedDB?
> 
> - Are there any other asynchronous DB APIs for dynamic languages that one could learn from (especially from mistakes that they have made)? They must have design principles and rationales one might be able to use. WebDatabase (minus schema plus cursor) looks nice.
> 
> On Jan 10, 2011, at 23:40 , Keean Schupke wrote:
> 
>> Hi, 
>> 
>> I did say it was for fun!  If you think it should be suggested somewhere I am happy to do so. Note that  I renamed 'onsuccess' to 'bind' to show how it works as a monad, there is no need to do this (although I prefer to it to explicitly show it is a Monad).
>> 
>> The definition of unit is simply:
>> 
>> var unit = function(v) {
>>     return {
>>         onsuccess: function(f) {f(v);}
>>     };
>> };
>> 
>> And then you can compose callbacks using 'onsuccess'...
>> 
>> you might like to keep onsuccess, and use "result" instead of "unit"... So simply using the above definition you can compose callbacks:
>> 
>> var y = db.transaction(["foo"]).objectStore("foo").getM(mykey1).onsuccess(function(result1) {
>>     db.transaction(["foo"]).objectStore("foo").getM(mykey2).onsuccess(function(result2) {
>>         result(result1 + result2);
>>     });
>> }); 
>> 
>> 
>> Cheers,
>> Keean.
>> 
>> 
>> On 10 January 2011 22:31, Jonas Sicking <jonas@sicking.cc> wrote:
>> This seems like something better suggeseted to the lists at ECMA where
>> javascript (or rather ECMAScript) is being standardized. I hardly
>> think that a database API like indexedDB is the place to redefine how
>> javascript should handle asynchronous programming.
>> 
>> / Jonas
>> 
>> On Mon, Jan 10, 2011 at 2:26 PM, Keean Schupke <keean@fry-it.com> wrote:
>> > Just to correct my cut and paste error, that was of course supposed to be:
>> > var y = do {
>> >     result1 <- db.transaction(["foo"]).objectStore("foo").getM(mykey1);
>> >     result2 <- db.transaction(["foo"]).objectStore("foo").getM(mykey2);
>> >     unit(result1 + result2);
>> > }
>> >
>> > Cheers,
>> > Keean.
>> > On 10 January 2011 22:24, Keean Schupke <keean@fry-it.com> wrote:
>> >>
>> >> Okay, sorry, the original change seemed sensible, I guess I didn't see how
>> >> you got from there to promises.
>> >>
>> >> Here's some fun to think about as an alternative though:
>> >>
>> >> Interestingly the pattern of multiple callbacks, providing each callback
>> >> is passed zero or one parameter forms a Monad.
>> >> So for example if 'unit' is the constructor for the object returned from
>> >> "get" then onsuccess it 'bind' and I can show that these obey the 3 monad
>> >> laws. Allowing composability of callbacks. So you effectively have:
>> >> var x = db.transaction(["foo"]).objectStore("foo").getM(mykey);
>> >> var y =
>> >> db.transaction(["foo"]).objectStore("foo").getM(mykey1).bind(function(result1)
>> >> {
>> >>
>> >>  db.transaction(["foo"]).objectStore("foo").getM(mykey2).bind(function(result2)
>> >> {
>> >>         unit(result1 + result2);
>> >>     });
>> >> });
>> >> The two objects returned "x" and "y" are both the same kind of object. y
>> >> represents the sum or concatination of the results of the lookups "mykey1"
>> >> and "mykey2". You would use it identically to using the result of a single
>> >> lookup:
>> >> x.bind(function(result) {... display the result of a single lookup ...});
>> >> y.bind(function(result) {... display the result of both lookups ...});
>> >>
>> >> If we could then have some syntactic sugar for this like haskell's do
>> >> notation we could write:
>> >> var y = do {
>> >>     db.transaction(["foo"]).objectStore("foo").getM(mykey1);
>> >>     result1 <- db.transaction(["foo"]).objectStore("foo").getM(mykey2);
>> >>     result2 <- db.transaction(["foo"]).objectStore("foo").getM(mykey2);
>> >>     unit(result1 + result2);
>> >> }
>> >> Which would be a very neat way of chaining callbacks...
>> >>
>> >> Cheers,
>> >> Keean.
>> >>
>> >> On 10 January 2011 22:00, Keean Schupke <keean@fry-it.com> wrote:
>> >>>
>> >>> Whats wrong with callbacks? To me this seems an unnecessary complication.
>> >>> Presumably you would do:
>> >>> var promise = db.transaction(["foo"]).objectStore("foo").get(mykey);
>> >>> var result = promise.get();
>> >>> if (!result) {
>> >>>     promise.onsuccess(function(res) {...X...});
>> >>> } else {
>> >>>     ...Y...
>> >>> }
>> >>>
>> >>> So you end up having to duplicate code at X and Y to do the same thing
>> >>> directly or in the context of a callback. Or you define a function to
>> >>> process the result:
>> >>> var f = function(res) {...X...};
>> >>> var promise = db.transaction(["foo"]).objectStore("foo").get(mykey);
>> >>> var result = promise.get();
>> >>> if (!result) {
>> >>>     promise.onsuccess(f);
>> >>> } else {
>> >>>     f(result)
>> >>> };
>> >>> But in which case what advantage does all this extra clutter offer over:
>> >>>
>> >>> db.transaction(["foo"]).objectStore("foo").get(mykey).onsuccess(function(res)
>> >>> {...X...});
>> >>>
>> >>> I am just wondering whether the change is worth the added complexity?
>> >>>
>> >>> Cheers,
>> >>> Keean.
>> >>>
>> >>> On 10 January 2011 21:31, Jonas Sicking <jonas@sicking.cc> wrote:
>> >>>>
>> >>>> I did some outreach to developers and while I didn't get a lot of
>> >>>> feedback, what I got was positive to this change.
>> >>>>
>> >>>> The basic use-case that was brought up was implementing a promises
>> >>>> which, as I understand it, works similar to the request model I'm
>> >>>> proposing. I.e. you build up these "promise" objects which represent a
>> >>>> result which may or may not have arrived yet. At some point you can
>> >>>> either read the value out, or if it hasn't arrived yet, register a
>> >>>> callback for when the value arrives.
>> >>>>
>> >>>> It was pointed out that this is still possible with how the spec is
>> >>>> now, but it will probably result in that developers will come up with
>> >>>> conventions to set the result on the request themselves. This wouldn't
>> >>>> be terribly bad, but also seems nice if we can help them.
>> >>>>
>> >>>> / Jonas
>> >>>>
>> >>>> On Mon, Jan 10, 2011 at 8:13 AM, ben turner <bent.mozilla@gmail.com>
>> >>>> wrote:
>> >>>> > FWIW Jonas' proposed changes have been implemented and will be
>> >>>> > included in Firefox 4 Beta 9, due out in a few days.
>> >>>> >
>> >>>> > -Ben
>> >>>> >
>> >>>> > On Fri, Dec 10, 2010 at 12:47 PM, Jonas Sicking <jonas@sicking.cc>
>> >>>> > wrote:
>> >>>> >> I've been reaching out to get feedback, but no success yet. Will
>> >>>> >> re-poke.
>> >>>> >>
>> >>>> >> / Jonas
>> >>>> >>
>> >>>> >> On Fri, Dec 10, 2010 at 4:33 AM, Jeremy Orlow <jorlow@chromium.org>
>> >>>> >> wrote:
>> >>>> >>> Any additional thoughts on this?  If no one else cares, then we can
>> >>>> >>> go with
>> >>>> >>> Jonas' proposal (and we should file a bug).
>> >>>> >>> J
>> >>>> >>>
>> >>>> >>> On Thu, Nov 11, 2010 at 12:06 PM, Jeremy Orlow <jorlow@chromium.org>
>> >>>> >>> wrote:
>> >>>> >>>>
>> >>>> >>>> On Tue, Nov 9, 2010 at 11:35 AM, Jonas Sicking <jonas@sicking.cc>
>> >>>> >>>> wrote:
>> >>>> >>>>>
>> >>>> >>>>> Hi All,
>> >>>> >>>>>
>> >>>> >>>>> One of the things we briefly discussed at the summit was that we
>> >>>> >>>>> should make IDBErrorEvents have a .transaction. This since we are
>> >>>> >>>>> allowing you to place new requests from within error handlers, but
>> >>>> >>>>> we
>> >>>> >>>>> currently provide no way to get from an error handler to any
>> >>>> >>>>> useful
>> >>>> >>>>> objects. Instead developers will have to use closures to get to
>> >>>> >>>>> the
>> >>>> >>>>> transaction or other object stores.
>> >>>> >>>>>
>> >>>> >>>>> Another thing that is somewhat strange is that we only make the
>> >>>> >>>>> result
>> >>>> >>>>> available through the success event. There is no way after that to
>> >>>> >>>>> get
>> >>>> >>>>> it from the request. So instead we use special event interfaces
>> >>>> >>>>> with
>> >>>> >>>>> supply access to source, transaction and result.
>> >>>> >>>>>
>> >>>> >>>>> Compare this to how XMLHttpRequests work. Here the result and
>> >>>> >>>>> error
>> >>>> >>>>> code is available on the request object itself. The 'load' event,
>> >>>> >>>>> which is equivalent to our 'success' event didn't supply any
>> >>>> >>>>> information until we recently added progress event support. But
>> >>>> >>>>> still
>> >>>> >>>>> it only supplies information about the progress, not the actual
>> >>>> >>>>> value
>> >>>> >>>>> itself.
>> >>>> >>>>>
>> >>>> >>>>> One thing we could do is to move
>> >>>> >>>>>
>> >>>> >>>>> .source
>> >>>> >>>>> .transaction
>> >>>> >>>>> .result
>> >>>> >>>>> .error
>> >>>> >>>>>
>> >>>> >>>>> to IDBRequest. Then make "success" and "error" events be simple
>> >>>> >>>>> events
>> >>>> >>>>> which only implement the Event interface. I.e. we could get rid of
>> >>>> >>>>> the
>> >>>> >>>>> IDBEvent, IDBSuccessEvent, IDBTransactionEvent and IDBErrorEvent
>> >>>> >>>>> interfaces.
>> >>>> >>>>>
>> >>>> >>>>> We'd still have to keep IDBVersionChangeEvent, but it can inherit
>> >>>> >>>>> Event directly.
>> >>>> >>>>>
>> >>>> >>>>> The request created from IDBFactory.open would return a IDBRequest
>> >>>> >>>>> where .transaction and .source is null. We already fire a IDBEvent
>> >>>> >>>>> where .source is null (actually, the spec currently doesn't define
>> >>>> >>>>> what the source should be I see now).
>> >>>> >>>>>
>> >>>> >>>>>
>> >>>> >>>>> The only major downside with this setup that I can see is that the
>> >>>> >>>>> current syntax:
>> >>>> >>>>>
>> >>>> >>>>> db.transaction(["foo"]).objectStore("foo").get(mykey).onsuccess =
>> >>>> >>>>> function(e) {
>> >>>> >>>>>  alert(e.result);
>> >>>> >>>>> }
>> >>>> >>>>>
>> >>>> >>>>> would turn into the slightly more verbose
>> >>>> >>>>>
>> >>>> >>>>> db.transaction(["foo"]).objectStore("foo").get(mykey).onsuccess =
>> >>>> >>>>> function(e) {
>> >>>> >>>>>  alert(e.target.result);
>> >>>> >>>>> }
>> >>>> >>>>>
>> >>>> >>>>> (And note that with the error handling that we have discussed, the
>> >>>> >>>>> above code snippets are actually plausible (apart from the alert()
>> >>>> >>>>> of
>> >>>> >>>>> course)).
>> >>>> >>>>>
>> >>>> >>>>> The upside that I can see is that we behave more like
>> >>>> >>>>> XMLHttpRequest.
>> >>>> >>>>> It seems that people currently follow a coding pattern where they
>> >>>> >>>>> place a request and at some later point hand the request to
>> >>>> >>>>> another
>> >>>> >>>>> piece of code. At that point the code can either get the result
>> >>>> >>>>> from
>> >>>> >>>>> the .result property, or install a onload handler and wait for the
>> >>>> >>>>> result if it isn't yet available.
>> >>>> >>>>>
>> >>>> >>>>> However I only have anecdotal evidence that this is a common
>> >>>> >>>>> coding
>> >>>> >>>>> pattern, so not much to go on.
>> >>>> >>>>
>> >>>> >>>> Here's a counter proposal:  Let's add .transaction, .source, and
>> >>>> >>>> .result
>> >>>> >>>> to IDBEvent and just specify them to be null when there is no
>> >>>> >>>> transaction,
>> >>>> >>>> source, and/or result.  We then remove readyState from IDBResult as
>> >>>> >>>> it
>> >>>> >>>> serves no purpose.
>> >>>> >>>> What I'm proposing would result in an API that's much more similar
>> >>>> >>>> to what
>> >>>> >>>> we have at the moment, but would be a bit different than XHR.  It
>> >>>> >>>> is
>> >>>> >>>> definitely good to have similar patterns for developers to follow,
>> >>>> >>>> but I
>> >>>> >>>> feel as thought the model of IndexedDB is already pretty different
>> >>>> >>>> from XHR.
>> >>>> >>>>  For example, method calls are supplied parameters and return an
>> >>>> >>>> IDBRequest
>> >>>> >>>> object vs you using new to create the XHR object and then making
>> >>>> >>>> method
>> >>>> >>>> calls to set it up and then making a method call to start it.  In
>> >>>> >>>> fact, if
>> >>>> >>>> you think about it, there's really not that much XHR and IndexedDB
>> >>>> >>>> have in
>> >>>> >>>> common except that they use event handlers.
>> >>>> >>>> As for your proposal, let me think about it for a bit and forward
>> >>>> >>>> it on to
>> >>>> >>>> some people I know who are playing with IndexedDB already.
>> >>>> >>>> J
>> >>>> >>>
>> >>>> >>
>> >>>> >>
>> >>>> >
>> >>>>
>> >>>
>> >>
>> >
>> >
>> 
> 
> -- 
> Dr. Axel Rauschmayer
> Axel.Rauschmayer@ifi.lmu.de
> http://hypergraphs.de/
> ### Hyena: organize your ideas, free at hypergraphs.de/hyena/
> 
> 
> 
> 

-- 
Dr. Axel Rauschmayer
Axel.Rauschmayer@ifi.lmu.de
http://hypergraphs.de/
### Hyena: organize your ideas, free at hypergraphs.de/hyena/
Received on Tuesday, 11 January 2011 09:32:14 GMT

This archive was generated by hypermail 2.3.1 : Tuesday, 26 March 2013 18:49:42 GMT