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

Re: [IndexedDB] Events and requests

From: Keean Schupke <keean@fry-it.com>
Date: Tue, 11 Jan 2011 09:22:12 +0000
Message-ID: <AANLkTimTYBLxQ0Uh1ZR48+F9TuayfCOv__CcVhwaCvUN@mail.gmail.com>
To: Axel Rauschmayer <axel@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>
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/
>
>
>
>
Received on Tuesday, 11 January 2011 09:22:45 GMT

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