Re: [IndexedDB] Promises (WAS: Seeking pre-LCWD comments for Indexed Database API; deadline February 2)

On Thu, Mar 4, 2010 at 2:37 PM, Jeremy Orlow <jorlow@chromium.org> wrote:

> On Wed, Mar 3, 2010 at 8:48 PM, Kris Zyp <kris@sitepen.com> wrote:
>
>>  -----BEGIN PGP SIGNED MESSAGE-----
>> Hash: SHA1
>>
>>
>>
>> On 3/3/2010 4:01 AM, Jeremy Orlow wrote:
>> > On Wed, Mar 3, 2010 at 4:49 AM, Kris Zyp <kris@sitepen.com
>> > <mailto:kris@sitepen.com> <kris@sitepen.com>> wrote:
>> > [snip]
>>
>> >
>> >     > The promises would only have a
>> >     "then" method which would take in an
>> >
>> >     > onsuccess and onerror callback.  Both are optional.  The
>> >     onsuccess
>> >
>> >     > function should take in a single parameter which matches the
>> >     return
>> >
>> >     > value of the synchronous counterpart.  The onerror function
>> >     should
>> >
>> >     > take in an IDBDatabaseError.  If the callbacks are null,
>> >     undefined,
>> >
>> >     > or omitted, they're ignored.  If they're anything else, we should
>> >
>> >     > probably either raise an exception immediately or ignore them.
>> >
>> >     Yes.
>> >
>> >
>> > Any thoughts on whether we'd raise or ignore improper inputs?  I'm
>> > leaning towards raise since it would be deterministic and silently
>> > ignoring seems like a headache from a developer standpoint.
>>
>> Throwing an error on improper inputs is fine with me.
>>
>> >
>> >
>> >     > If there's an error, all onerror
>> >     callbacks would be called with the
>> >
>> >     > IDBDatabaseError.
>> >
>> >     Yes.
>> >
>> >
>> >     > Exceptions within callbacks
>> >     would be ignored.
>> >
>> >     With CommonJS promises, the promise returned by the then() call goes
>> >     into an error state if a callback throws an exception. For example,
>> >
>> >     someAsyncOperation.then(successHandler, function(){ throw new
>> >     Error("test") })
>> >         .then(null, function(error){ console.log(error); });
>> >
>> >     Would log the thrown error, effectively giving you a way of catching
>> >     the error.
>> >
>> >     Are you suggesting this as a simplification so that IndexedDB impls
>> >     doesn't have to worry about recursive creation of promises? If so, I
>> >     suppose that seems like a reasonable simplification to me.
>> >     Although if
>> >     promises are something that could be potentially reused in other
>> >     specs, it would be nice to have a quality solution, and I don't
>> >     think
>> >     this is a big implementation burden, I've implemented the recursive
>> >     capabilities in dozen or two lines of JS code. But if burden is too
>> >     onerous, I am fine with the simplification.
>> >
>> >
>> > When you say "recursive capabilities" are you just talking about how
>> > to handle exceptions, or something more?
>> >
>> > In terms of exceptions: I don't think it's an
>> > enormous implementational burden and thus I think it's fine to
>> > ignore that part of the equation.  So the question mainly comes down
>> > to whether the added complexity is worth it.  Can you think of any
>> > real-world examples of when this capability is useful in promises?
>> >  If so, that'd definitely help us understand the pro's and con's.
>>
>> Maybe I misunderstanding your suggestion. By "recursive capability" I
>> meant having then() return a promise (that is fulfilled with the
>> result of executing the callback), and I thought you were suggesting
>> that instead, then() would not return a promise. If then() returns a
>> promise, I think the returned promise should clearly go into an error
>> state if the callback throws an error. The goal of promises is to
>> asynchronously model computations, and if a computation throws, it
>> should result in the associated promise entering error state. The
>> promise returned by then() exists to represent the result of the
>> execution of the callback, and so it should resolve to the value
>> returned by the callback or an error if the callback throws. Silenty
>> swallowing errors seems highly undesirable.
>>
>> Now if we are simplifying then() to not return a promise at all, than
>> I would think callbacks would just behave like any other event
>> listener in regards to uncaught errors.
>>
>
> You are quite right!  I misunderstood how this part of promises worked.
>
> Is there excitement about speccing promises in general?  If not, it seems a
> little odd to spec such a powerful mechanism into just IndexedDB....and it
> might be best to spec the simplified version of .then(): .then() will return
> undefined, onsuccess/onerror's return values will be swallowed, and any
> thrown exceptions will be thrown.
>

Er....thrown exceptions will be _swallowed_ (not thrown).


> This should make it easy to make IndexedDB support full blown promises
> if/whenever they're specced.  (It's not clear to me whether UA support for
> them would offer enough advantages to warrant it.)
>
> It sounds like you're OK with such an approach, Kris?
>
> What do others think?
>
> J
>
>
>>  >
>> >     > In terms of speccing, I'm not sure if we can get away with
>> >     speccing
>> >
>> >     > one promise interface or whether we'd need to create one for each
>> >
>> >     > type of promise.
>> >
>> >     Certainly the intent of promises is that there is exists only one
>> >     generic promise interface that can be reused everywhere, at
>> >     least from
>> >     the JS perspective, not sure if the extra type constraints in IDL
>> >     demand multiple interfaces to model promise's effectively
>> >     parameterized generic type form.
>> >
>> >
>> > Unfortunately, I don't really know.  Before we try speccing it, I'll
>> > definitely see if any WebIDL experts have suggestions.
>> >
>> >
>> > Also, do we want to explicitly spec what happens in the following case?
>> >
>> > window.indexedDB.open(...).then(
>> >     function(db) {  db.openObjectStore("a").then( function(os) {
>> > alert("Opened a"); } ) }
>> > ).then(
>> >     function(db) { alert("Second db opened"); }
>> > );
>> >
>> > Clearly the first function(db) is called first.  But the question is
>> > whether it'd be a race of which alert is called first or whether the
>> > "Second db opened" alert should always be shown first (since clearly
>> > if the first is called, the second _can_ be fired immediately
>> > afterwards).
>> >
>> > I'm on the fence about whether it'd be useful to spec that the
>> > entire chain needs to be called one after the other before calling
>> > any other callbacks.  Does anyone have thoughts on whether this is
>> > useful or not?  If we do spec it to call the entire chain, then what
>> > happens if inside one of the callbacks, something is added to the
>> > chain (via another .then() call).
>> >
>> Specing the order of multiple events in the event loop seems like it
>> would be excessive burden on implementors, IMO.
>>
>> > I've been talking to a co-worker here who seems to know a decent
>> > amount about promises (as implemented in E) and some about differed
>> > (as implemented in Python's Twisted library).  From talking to him,
>> > it seems that my original suggestion for not handling exceptions
>> > thrown inside a .then() callback is the way to go.
>> >
>> > It seems as though promises put a lot of weight on composability and
>> > making it so that the order of .then() calls not mattering.  This
>> > means that you can then pass promises to other async interfaces and
>> > not have to worry about different timings leading to different
>> > results.  It also means that if you pass a promise into multiple
>> > consumers (say, javascript libraries) you don't need to worry about
>> > one using a promise in a way that screws up another.
>> >
>> > Differed seems to be more expressive and flexible.  For example,
>> > instead of doing this:
>> >
>> > window.indexedDB.open(...).then(
>> >     function(db) {  db.openObjectStore("a").then(
>> >         function(os) { os.get("x").then(
>> >             function(value) { alert("Value: " + value); }
>> >         ) }
>> >     ) }
>> > );
>> >
>> > I could do this:
>> >
>> > window.indexedDB.open(...).then(
>>
>> >     function(db) { return db.openObjectStore("a"); }    // Note the
>> > return value is passed on to the next step.
>> > ).then(
>> >     function(os) { return os.get("x"); }
>> > ).then(
>> >     function(value) { alert("Value: " + value); }
>> > );
>> >
>> > And I can also have any one of those throw an error which would be
>> > passed on to the rest of the chain, much like nested try/catch
>> > blocks in single threaded code.
>>
>>
>> Yes, this type of chaining is exactly the intent of the promises I
>> have suggested. Having the return value of the thrown error passed on
>> to the rest of the chain is most intuitive IMO (since it is analagous
>> to synchronous call stacks) and what I would recommend. One thing that
>> Twisted got wrong (but E got right) was that the callbacks in
>> Twisted's deferred mutate the original deferred itself which causes
>> difficult to contain side-effects, whereas the callback's return value
>> should only affect a newly generated returned promise (maintaining
>> proper functional data flow of outputs not affecting inputs).
>>
>>
>> - --
>> Kris Zyp
>> SitePen
>> (503) 806-1841
>> http://sitepen.com
>> -----BEGIN PGP SIGNATURE-----
>> Version: GnuPG v1.4.9 (MingW32)
>> Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/
>>
>> iEYEARECAAYFAkuOywcACgkQ9VpNnHc4zAzWCQCgiTl7S2Iv477vuzH2+IG6raMc
>> jrsAoL4aUfbG7WWHUktwbid8MMw0C5C6
>> =PVcB
>> -----END PGP SIGNATURE-----
>>
>>
>

Received on Thursday, 4 March 2010 14:45:11 UTC