- From: Keean Schupke <keean@fry-it.com>
- Date: Mon, 10 Jan 2011 22:40:56 +0000
- To: Jonas Sicking <jonas@sicking.cc>
- Cc: ben turner <bent.mozilla@gmail.com>, Jeremy Orlow <jorlow@chromium.org>, Webapps WG <public-webapps@w3.org>
- Message-ID: <AANLkTik+DLaq+PGvASOEDuYjFgGeGg2JVGL4GyJNhfxc@mail.gmail.com>
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 > >>>> >>> > >>>> >> > >>>> >> > >>>> > > >>>> > >>> > >> > > > > >
Received on Monday, 10 January 2011 22:41:28 UTC