Promises Proposal

Dave Townsend dtownsend at mozilla.com
Wed Apr 3 16:25:46 UTC 2013


Including firefox-dev in this discussion like I should have done in the
first place. For context we have some issues with the existing promises
implementation and so we're going over the basic ideas to make sure we get
an implementation that we want.

On Tue, Apr 2, 2013 at 2:47 AM, David Rajchenbach-Teller <
dteller at mozilla.com> wrote:

> Thanks to Joe and yourself for getting this started.
>
> On 4/1/13 7:37 PM, Dave Townsend wrote:
> > I've been working through the discussion on the etherpad and I'll like
> > to push to some resolution here. I think it makes sense to do this in
> > two steps. First I'd like us to agree on the API and behaviour we want
> > from promises, once we have that we will just need to choose an
> > implementation and a place to put it. Please let me know if you see any
> > show-stopping issues with what I've laid out here, otherwise we'll talk
> > about implementation choices next.
> >
> > Core API:
> >
> > I think the core API that makes up a promises library is pretty well
> > agreed upon:
> >
> > function defer() {
> >    let deferred = {
> >         resolve: function(value) {},
> >         reject: function(value) {},
> >         promise: {
> >             then: function(onResolved, onRejected) {}
> >         }
> >     };
> >
> >     return deferred;
> > }
>
> I believe that we rather want
>
> function defer(options) {
>    // ...
>    promise: {
>       then: function(onResolved, onRejected, options) {
>          // ...
>       }
>    }
> }
>
> Where |options| is used to customize the behavior of the promise or the
> promise chain by any means necessary, e.g. [a]synchronicity, turning
> timeouts on/off, customizing logging, etc.
>

Yeah I like this.

let promise = Promise.defer(options);
> promise = promise.then(...);  // Carry the options
> promise = promise.then(...);  // Carry the options
> promise = promise.then(...);  // Carry the options
> let left = promise.then(...); // Carry the options for this subchain
> let right = promise.then(..., ..., newOptions); // Replaces the options
> for this subchain
>

I don't think we should do this though. I'm against making consumers rely
on the promise implementation including anything other than the standard
functionality. I believe you can get the same result though with this:

let deferred = defer(newOptions);
promise.then(deferred.resolve, deferred.reject);
let right = deferred.promise;


>
> > Aside from bikeshedding over names hopefully that is non-controversial.
> > Likewise some syntax sugar like resolved and rejected functions would
> > make sense.
> >
> > Exposing state:
> >
> > For debuggability there is enough need that we should include a way to
> > expose the state of a promise. I want to do this by adding properties to
> > the deferred object, not the promise. There are two reasons for this,
> > first it keeps the promise API surface clean and stops API consumers
> > from considering relying on this exposed state, second it is easy to
> > hide by wrapping the deferred in an implementation that doesn't want to
> > expose it (say add-on SDK).
> >
> > This is different to the current A+ draft on this subject where they
> > expose it on the promise (though they don't define deferred at all of
> > course) but should that draft reach consensus and we think it useful we
> > could talk about also exposing this state on the promise at a later
> > date. That said, their inspectState method seems exactly what we should
> > add to the deferred (maybe with name changes to match those we already
> > use routinely):
> > https://github.com/promises-aplus/synchronous-inspection-spec/issues/6
>
> Sounds good.
>
> > Logging:
> >
> > Logging warnings when resolve/reject are called a second time seems a
> > no-brainer. This should never happen. Logging cases where a
> > resolve/reject goes unhandled is a larger question. Yoric's suggestion
> > that we log a warning on a timeout if it goes unhandled is a good one as
> > it feels rare that you'll add callbacks long after a promise is returned
>
> I can think of a few cases in which you want to do this. However, with
> my proposal above, we can simply mark this as normal/unexpected on a
> per-promise-chain basis.
>
> > Synchronicity:
> >
> > It seems clear that by default promises should be asynchronous and I'm
> > going to be specific, calls to resolve/reject should wait at least a
> > tick of the event loop before calling any callbacks and calls to then
> > must return before callbacks passed can be called. This matches the A+
> > spec, will keep stack size minimal and will also ensure code has some
> > reliable expectations about when it can be called.
>
> My main concern with an automatically asynchronous implementation is
> that it makes performance both hard to predict and hard to measure. This
> is going to make our life harder. I can probably work around this
> limitation with an appropriate option.
>

Can you elaborate on this some more, I don't think I actually understand
what your needs are here. Everyone else I've spoken to seems to be coming
from the standpoint that the more async the better, any operations we're
doing shouldn't be holding up the main thread any more than they can. What
circumstances do we have where that isn't the case?


> Note that paolo has implemented a synchronous version of Promise that
> ensures we run in constant stack space in most cases (if you're curious,
> he's basically unrolling tail recursion).
>
> > It is also clear that in some cases a synchronous promise is needed.
> > This can be in cases where APIs aren't synchronous yet we still want to
> > use a promise both for API consistency reasons and to support a
> > potential async future. Synchronous promises can also be easier to debug
> > as you can see a proper stack trace for callbacks.
>
> I'm sure that we can have an option that lets us capture stacks even in
> asynchronous mode.
>
> > We shouldn't make two implementations of promises if we can avoid it. It
> > is trivial to convert a synchronous promise implementation into an
> > asynchronous implementation with a wrapper, so we will do that. Wherever
> > people get promises from, be it SDK/core/promise or if it ends up in
> > toolkit, defer() should by default return an asynchronous promise,
> > however it should be possible to get a synchronous promise, either
> > through an alternate module or with something like syncDefer(). We
> > should only use the synchronous version where there is a clear need for
> it.
>
> I have some issues with promise living in SDK, for quite a few reasons:
>

There are some good questions in this but rather than confuse the immediate
promises discussion I'm going to split it off into a firefox-dev thread.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/firefox-dev/attachments/20130403/a2e79919/attachment.html>


More information about the firefox-dev mailing list