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

On Wed, Mar 3, 2010 at 11:01 AM, Jeremy Orlow <jorlow@chromium.org> wrote:

> On Wed, Mar 3, 2010 at 4:49 AM, Kris Zyp <kris@sitepen.com> wrote:
>
>>  -----BEGIN PGP SIGNED MESSAGE-----
>> Hash: SHA1
>>
>>
>>
>> On 3/1/2010 2:52 PM, Jeremy Orlow wrote:
>> > Thanks for the pointers.  I'm actually pretty sold on the general
>> > idea of promises, and my intuition is that there won't be a very
>> > big resource penalty for using an API like this rather than
>> > callbacks or what's currently specced.  At the same time, it seems
>> > as though there isn't much of a standard in terms of the precise
>> > semantics and some of the techniques (such as optionally taking
>> > callbacks and not returning a promise if they are supplied) seems
>> > like a decent answer for pure javascript APIs, but maybe not as
>> > good for IDL and a standard like this.
>> >
>> > Do you guys have any recommendations for the precise semantics we'd
>> > use, if we used promises in IndexedDB?  To get started, let me list
>> > what I'd propose and maybe you can offer counter proposals or
>> > feedback on what would or wouldn't work?
>> >
>> >
>> > Each method on a ____Request interface (the async ones in the spec)
>> > whose counterpart returns something other than void would instead
>> > return a Promise.
>>
>> Asynchronous counterparts to void-returning synchronous functions can
>> still return promises. The promise would just resolve to undefined,
>> but it still fulfills the role of indicating when the operation is
>> complete.
>>
>
> Good point!  Silly me.
>
>
>>  > 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.
>
>
>> > 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.
>

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.

I'm not sure if this added flexibility is worth the potentially more
confusing and subtle code + the increased API surface area, though.  The
more straightforward composability and predictability of promises also seems
appealing.

So, at this point, I'm actually leaning towards saying all exceptions should
just be swallowed up by the then clause.  If a particular implementation did
want to handle these situations, there's nothing stopping them from using a
bit of global state to keep track of it (though I admit that's less
elegent).  It's also worth noting that it'd be possible to implement
differed semantics with a wrapper around promises.

(Just to reiterate, I am not an expert on any of this, so please correct me
if I'm misunderstanding/mischaracterizing.  My goal here is to find a
cleaner way to do the IndexedDB async API.)


> > 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).
>
> J
>

Received on Wednesday, 3 March 2010 16:59:27 UTC