Re: Promises Proposal

29 views
Skip to first unread message

Dave Townsend

unread,
Apr 3, 2013, 12:25:46 PM4/3/13
to David Rajchenbach-Teller, firef...@mozilla.org, Panos Astithas, Joe Walker, Gavin Sharp, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Paolo Amadini, Jordan Santell
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 <dte...@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.

Gregory Szorc

unread,
Apr 3, 2013, 3:45:39 PM4/3/13
to David Rajchenbach-Teller, Panos Astithas, Joe Walker, Gavin Sharp, Dave Townsend, Irakli Gozalishvili, Richard Newman, Dave Camp, Paolo Amadini, firef...@mozilla.org, Jordan Santell
On 4/3/13 12:33 PM, David Rajchenbach-Teller wrote:
> Finally, this means turning promises into a poor man's multi-threading
> system. One that still clogs the main thread in subtle and unpredictable
> manners (this includes GC, by the way). We already have a very nice
> multi-threading system that lacks libraries. I would prefer if we added
> the missing libraries to chrome workers instead of emulating them
> poorly. This is the policy of project Async & Responsive, that will be
> announced in a few days, by the way.

This a thousand times over. We will continue to add more code to the
main thread until chrome workers support accessing the subsystems and
APIs needed to support features.

And, once you are on a worker thread, it's also arguably allowable to
use *gasp* synchronous APIs for things like I/O. This would eliminate a
lot of callback spaghetti, making code easier to read and eliminating
concerns over performance of promises since they won't be necessary.
Now, there are real concerns about mixing synchronous and asynchronous
APIs and exposing a common set of APIs between chrome and non-chrome.
But, at least you have the *option* to write clean, procedural, blocking
code from worker threads.
_______________________________________________
firefox-dev mailing list
firef...@mozilla.org
https://mail.mozilla.org/listinfo/firefox-dev

David Rajchenbach-Teller

unread,
Apr 3, 2013, 3:36:02 PM4/3/13
to Dave Townsend, Panos Astithas, Joe Walker, Gavin Sharp, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Paolo Amadini, firef...@mozilla.org, Jordan Santell
On 4/3/13 6:25 PM, Dave Townsend wrote:
> 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.

Well, if you believe that extending the crowd will make it easier to
reach a consensus, by all means, let's do it :)

[..]
> 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;

Works for me.

[...]

> > 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?

My point above is that of being able to accurately measure performance.
We cannot improve what we cannot measure, and chopping simple operations
across hundreds of events that are evaluated "whenever we can" will make
things quite difficult. As mentioned, there may be a way to work around
this, by hardcoding performance measurement inside promises themselves.
However, this might end up rather expensive.

Secondly, chopping simple operations more than necessary will
considerably decrease throughput of these operations. I am talking about
orders of magnitude slower (needs benchmarking, of course). This might
be a pretty steep cost for having a somewhat foolproof way of increasing
reactivity.

Finally, this means turning promises into a poor man's multi-threading
system. One that still clogs the main thread in subtle and unpredictable
manners (this includes GC, by the way). We already have a very nice
multi-threading system that lacks libraries. I would prefer if we added
the missing libraries to chrome workers instead of emulating them
poorly. This is the policy of project Async & Responsive, that will be
announced in a few days, by the way.

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

Ok.

Cheers,
David

--
David Rajchenbach-Teller, PhD
Performance Team, Mozilla

Mark Finkle

unread,
Apr 3, 2013, 4:38:34 PM4/3/13
to Gregory Szorc, Panos Astithas, Joe Walker, Dave Camp, Dave Townsend, Irakli Gozalishvili, Richard Newman, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, firef...@mozilla.org, Jordan Santell
On 4/3/13 12:33 PM, David Rajchenbach-Teller wrote:
> Finally, this means turning promises into a poor man's multi-threading
> system. One that still clogs the main thread in subtle and unpredictable
> manners (this includes GC, by the way). We already have a very nice
> multi-threading system that lacks libraries. I would prefer if we added
> the missing libraries to chrome workers instead of emulating them
> poorly. This is the policy of project Async & Responsive, that will be
> announced in a few days, by the way.

This a thousand times over. We will continue to add more code to the
main thread until chrome workers support accessing the subsystems and
APIs needed to support features.
I like this approach too (well maybe a little less than Greg), but for all the same reasons. We do have issues with chrome workers creating a noticeable memory hit, but we should find ways to fix that anyway.

Gavin Sharp

unread,
Apr 3, 2013, 7:38:09 PM4/3/13
to David Rajchenbach-Teller, firef...@mozilla.org, Dave Townsend
On Wed, Apr 3, 2013 at 12:36 PM, David Rajchenbach-Teller
<dte...@mozilla.com> wrote:
> Well, if you believe that extending the crowd will make it easier to
> reach a consensus, by all means, let's do it :)

(Aside: it's possible that reaching consensus in a private thread
would be easier, though I'm not sure I believe that's actually true -
this list and its moderation policies are young! But consensus reached
that way wouldn't have the same value in an open source project, and
reaching consensus isn't the only goal. Decisions made that way
deprive other participants (including potential future ones) from
contributing to and benefiting from the discussion. I know you know
this, but I couldn't let the side comment go unaddressed :)

> Finally, this means turning promises into a poor man's multi-threading
> system. One that still clogs the main thread in subtle and unpredictable
> manners (this includes GC, by the way). We already have a very nice
> multi-threading system that lacks libraries. I would prefer if we added
> the missing libraries to chrome workers instead of emulating them
> poorly. This is the policy of project Async & Responsive, that will be
> announced in a few days, by the way.

That's a bit of an over-statement - the goal of making Promises
resolution default-asynchronous isn't to implement a "poor man's
multi-threading system". I don't think anyone would object to the
improvements you're suggesting for workers (it's a great idea, and I'm
glad you and others seem motivated to act). I also agree that we
should be using them instead of Promises with async APIs on the main
thread, where possible. But despite that, Promises are useful and we
will continue to make use of them, and so we should take care to have
the default API make the right tradeoffs.

It's obviously difficult to agree on what the right tradeoff is in the
abstract, because everyone has different perspectives about how
they're likely to be used in practice. The "hard to measure"/"reduced
throughput for sequential tasks" vs. "easy to cause runaway stack
growth"/"unpredictable behavior" tradeoff depends a lot on
perspective, and even on the specific use cases where Promises have
been used. So I don't think we'll reach consensus one way or the other
in the abstract.

It sounds like we're narrowing towards a minimal, shared
implementation with synchronous resolution, with a more
widely-exposed/general purpose implementation built on top of it
offers asynchronous resolution, with the option of using synchronous
resolution in exceptional cases, where it is appropriate.

Gavin

Richard Newman

unread,
Apr 3, 2013, 7:50:33 PM4/3/13
to Gavin Sharp, David Rajchenbach-Teller, Dave Townsend, firef...@mozilla.org
> It's obviously difficult to agree on what the right tradeoff is in the
> abstract, because everyone has different perspectives about how
> they're likely to be used in practice. The "hard to measure"/"reduced
> throughput for sequential tasks" vs. "easy to cause runaway stack
> growth"/"unpredictable behavior" tradeoff depends a lot on
> perspective, and even on the specific use cases where Promises have
> been used. So I don't think we'll reach consensus one way or the other
> in the abstract.
>
> It sounds like we're narrowing towards a minimal, shared
> implementation with synchronous resolution, with a more
> widely-exposed/general purpose implementation built on top of it
> offers asynchronous resolution, with the option of using synchronous
> resolution in exceptional cases, where it is appropriate.


Responding to these two paragraphs:

Taking a pragmatic view -- if we shipped synchronous-only promises, we'd see consumers start to include hacks to avoid it. And so we have:

http://hg.mozilla.org/mozilla-central/file/default/services/common/utils.js#l112

And if we shipped async-only promises, someone would do similar work to get guaranteed-synchronous versions, or they'd build some sibling of promises that achieved their goals.

So we ought to ship both. That might be a given, but I think it's worth stating.

The question, then, is where to put the ease-of-use slider.

I think encouraging async by default is a reasonable starting position, but earlier mentions in this thread of existing work to avoid stack blowout for synchronous resolution, and David's concerns about measurement and perf, take away much of the incentive. I'd almost go so far as to say that chrome code should be synchronous by default (if we can guarantee stack-safety), but this is not a choice I'd be happy making for all time.

Is there a reason why we shouldn't peg the slider right in the middle, and have both sync and async resolution be of approximately equal class?

Gavin Sharp

unread,
Apr 3, 2013, 8:03:14 PM4/3/13
to Richard Newman, David Rajchenbach-Teller, firef...@mozilla.org, Dave Townsend
On Wed, Apr 3, 2013 at 4:50 PM, Richard Newman <rne...@mozilla.com> wrote:
> Is there a reason why we shouldn't peg the slider right in the middle, and have both sync and async resolution be of approximately equal class?

Well, at the very least we don't need to let finding the right final
position for that slider block us landing code, except insofar as it
impacts implementation details - and I don't think it does at all.

Gavin

Richard Newman

unread,
Apr 3, 2013, 8:13:43 PM4/3/13
to ga...@gavinsharp.com, dte...@mozilla.com, firef...@mozilla.org, dtow...@mozilla.com
Agreed; mostly just keeping multiple plates spinning!

(Sent from phone.)

David Rajchenbach-Teller

unread,
Apr 4, 2013, 5:16:41 AM4/4/13
to Gavin Sharp, firef...@mozilla.org, Dave Townsend
On 4/4/13 1:38 AM, Gavin Sharp wrote:
> It's obviously difficult to agree on what the right tradeoff is in the
> abstract, because everyone has different perspectives about how
> they're likely to be used in practice. The "hard to measure"/"reduced
> throughput for sequential tasks" vs. "easy to cause runaway stack
> growth"/"unpredictable behavior" tradeoff depends a lot on
> perspective, and even on the specific use cases where Promises have
> been used. So I don't think we'll reach consensus one way or the other
> in the abstract.

Actually, there is no tradeoff in your list.
With Paolo's proposal, we can already have all of:
- don't make measurement harder than they are;
- don't reduce throughput (possibly actually improve it somewhat);
- eliminate runaway stack growth in almost all cases;
- don't make behavior any harder to predict.

By combining it with code instrumentation for debugging, we can end up
with almost everything we need.

> It sounds like we're narrowing towards a minimal, shared
> implementation with synchronous resolution, with a more
> widely-exposed/general purpose implementation built on top of it
> offers asynchronous resolution, with the option of using synchronous
> resolution in exceptional cases, where it is appropriate.

Actually, I would be interested in knowing whether we have anyone
involved in this conversation who would use this asynchronous-by-default
implementation.

Cheers,
David


--
David Rajchenbach-Teller, PhD
Performance Team, Mozilla

Paolo Amadini

unread,
Apr 4, 2013, 10:55:17 AM4/4/13
to firef...@mozilla.org
On 04/04/2013 1.50, Richard Newman wrote:
> And so we have:
>
> http://hg.mozilla.org/mozilla-central/file/default/services/common/utils.js#l112

If the code comment reflects the reasons behind implementing this helper
function to delay promise resolution, there are two points made there:
* Prevent stack accumulation
* Prevent callers from accidentally relying on same-tick resolution

The first one already has a patch.

About the second one, I'd argue that the converse is also true: if we
had a promise implementation that always waited a tick on resolution,
callers might accidentally rely on that behavior as well.

And, code that makes assumptions on one of those behaviors is probably
already subject to other race conditions, and would be subject to them
even if it used a structure based on callbacks, and not promises.

I think we should have non-delayed resolution when the module is used
by Toolkit code, to improve performance. Strangely, I seem to remember
that Irakli mentioned that the Add-on SDK also wanted non-delayed
resolution (though I can't find the exact post right now). So, I'm not
sure if there is actually someone that thinks delayed resolution is
needed for other reasons than to solve the stack accumulation problem.

If there are no strong objections to non-delayed resolution, I'd like
to proceed with finalizing that implementation (for now, you can see
a prototype in bug 810490). If someone can find a good way to isolate
the returned Promise objects so that consumers don't have access to
internals, I'd be glad to implement that, so that we can plug the new
implementation seamlessly into the Add-on SDK as well.

Cheers,
Paolo

Dave Townsend

unread,
Apr 4, 2013, 1:42:40 PM4/4/13
to Paolo Amadini, Gavin Sharp, Richard Newman, David Rajchenbach-Teller, Firefox Dev, Irakli Gozalishvili
On Thu, Apr 4, 2013 at 7:17 AM, Paolo Amadini <paolo....@amadzone.org> wrote:
On 04/04/2013 1.50, Richard Newman wrote:
If the code comment reflects the reasons behind implementing this helper
function to delay promise resolution, there are two points made there:
 * Prevent stack accumulation
 * Prevent callers from accidentally relying on same-tick resolution

The first one already has a patch.

About the second one, I'd argue that the converse is also true: if we
had a promise implementation that always waited a tick on resolution,
callers might accidentally rely on that behavior as well.

And, code that makes assumptions on one of those behaviors is probably
already subject to other race conditions, and would be subject to them
even if it used a structure based on callbacks, and not promises.

I think we should have non-delayed resolution when the module is used
by Toolkit code, to improve performance. Strangely, I seem to remember
that Irakli mentioned that the Add-on SDK also wanted non-delayed
resolution (though I can't find the exact post right now). So, I'm not
sure if there is actually someone that thinks delayed resolution is
needed for other reasons than to solve the stack accumulation problem.

Strange, I could swear that I talked with people about async promises and heard only good things, but perhaps as you say it was purely for stack saving purposes. Is async resolution the only concern, what about async callback attachment (which is required by the A+ spec)?

rnewman seemed to indicate support for this earlier in the thread, Richard?
 

If there are no strong objections to non-delayed resolution, I'd like
to proceed with finalizing that implementation (for now, you can see
a prototype in bug 810490). If someone can find a good way to isolate
the returned Promise objects so that consumers don't have access to
internals, I'd be glad to implement that, so that we can plug the new
implementation seamlessly into the Add-on SDK as well.

If we put the implementation in toolkit then SDK can just wrap it to only expose the minimal API we want.

Mike Connor

unread,
Apr 4, 2013, 2:11:43 PM4/4/13
to Dave Townsend, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Irakli Gozalishvili, Richard Newman, Firefox Dev
On 2013-04-04 1:42 PM, Dave Townsend wrote:
On Thu, Apr 4, 2013 at 7:17 AM, Paolo Amadini <paolo....@amadzone.org> wrote:
On 04/04/2013 1.50, Richard Newman wrote:
> And so we have:
>
> http://hg.mozilla.org/mozilla-central/file/default/services/common/utils.js#l112

If the code comment reflects the reasons behind implementing this helper
function to delay promise resolution, there are two points made there:
 * Prevent stack accumulation
 * Prevent callers from accidentally relying on same-tick resolution

The first one already has a patch.

About the second one, I'd argue that the converse is also true: if we
had a promise implementation that always waited a tick on resolution,
callers might accidentally rely on that behavior as well.

And, code that makes assumptions on one of those behaviors is probably
already subject to other race conditions, and would be subject to them
even if it used a structure based on callbacks, and not promises.

I think we should have non-delayed resolution when the module is used
by Toolkit code, to improve performance. Strangely, I seem to remember
that Irakli mentioned that the Add-on SDK also wanted non-delayed
resolution (though I can't find the exact post right now). So, I'm not
sure if there is actually someone that thinks delayed resolution is
needed for other reasons than to solve the stack accumulation problem.

Strange, I could swear that I talked with people about async promises and heard only good things, but perhaps as you say it was purely for stack saving purposes. Is async resolution the only concern, what about async callback attachment (which is required by the A+ spec)?

rnewman seemed to indicate support for this earlier in the thread, Richard?

This is sort of the meta-question around promises.  What are we using them for, and why?  My understanding is that promises are useful precisely because they can be chained like sync calls, while being/enabling async work.  I'm not sure what the value proposition is for promises if resolution is always immediate, why not use return/throw like normal JS in that case?

-- Mike

Gavin Sharp

unread,
Apr 4, 2013, 2:31:31 PM4/4/13
to Mike Connor, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Firefox Dev
On Thu, Apr 4, 2013 at 11:11 AM, Mike Connor <mco...@mozilla.com> wrote:
> This is sort of the meta-question around promises. What are we using them
> for, and why? My understanding is that promises are useful precisely
> because they can be chained like sync calls, while being/enabling async
> work. I'm not sure what the value proposition is for promises if resolution
> is always immediate, why not use return/throw like normal JS in that case?

"promise resolution is synchronous" and "enables async chaining" are
orthogonal. The debate is about whether to enforce that, when a
promise is resolved (often but not always as a result of an async
operation), each step in the rest of the chain yields to the event
loop before being invoked.

Gavin

David Rajchenbach-Teller

unread,
Apr 4, 2013, 2:35:27 PM4/4/13
to Mike Connor, Gavin Sharp, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Firefox Dev
Let's fix some vocabulary.

Asynchronous === "I don't know when the call will complete"
Deferred === "The call will not complete on this tick"

Promises are invaluable in presence of asynchronicity. However, this
does not mean that we need to defer every single evaluation, nor does
this mean that promises themselves should enforce deferred evaluation.

For instance, the implementation of OS.File:
- defers most I/O to a worker;
- chains promises without deferring any further to add error handling,
resource cleanup, present the result, etc.

Cheers,
David

On 4/4/13 8:11 PM, Mike Connor wrote:
> This is sort of the meta-question around promises. What are we using
> them for, and why? My understanding is that promises are useful
> precisely because they can be chained like sync calls, while
> being/enabling async work. I'm not sure what the value proposition is
> for promises if resolution is always immediate, why not use return/throw
> like normal JS in that case?
>
> -- Mike


--
David Rajchenbach-Teller, PhD
Performance Team, Mozilla

Dave Camp

unread,
Apr 4, 2013, 2:47:57 PM4/4/13
to David Rajchenbach-Teller, Gavin Sharp, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev
I think the problem some people have with non-deferred-promises is that given the following code:
---
foo().then( ){
  dump("1");
  return nextTickPromise();
}).then(function() {
  dump("2");
});

dump("3");
---

Whether you get "123" or "312" depends on the implementation of foo().  And if it's sync today, you have no guarantee that it will stay that way (like mconnor said - if you had such a guarantee you wouldn't be using promises).  With async promises you can guarantee that order will be 321.

Unless every consumer of your library is careful, that's going to be error prone and hard to test.  The important thing here is that the bugs are going to happen because *users* of foo() aren't careful, not because the *implementor* of foo isn't careful.  So whether foo() can choose to use defer() or syncDefer() doesn't really help anything.

So we basically need to ask if we expect everyone to be able to carefully write their code to handle 312 vs. 123 safe every time (probably without testing, because I unless we mock every promise-returning function to provide a sync vs. async problem the testing will be insufficient).  I don't think we can.

I'd say we should defer the decision of whether the caller is going to be careful enough to the caller, not the callee.  The above snippet should print 321 by default, no matter how foo() is implemented, but if I'm gutsy enough to take that ambiguity into my hands I can do;

foo().sync().then() {
}
... etc.

-dave

Matt Brubeck

unread,
Apr 4, 2013, 2:57:43 PM4/4/13
to Dave Camp, David Rajchenbach-Teller, Gavin Sharp, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev
On 4/4/2013 11:47 AM, Dave Camp wrote:
> I think the problem some people have with non-deferred-promises is
> that given the following code:
>
> [...]
>
> Whether you get "123" or "312" depends on the implementation of
> foo(). And if it's sync today, you have no guarantee that it will
> stay that way (like mconnor said - if you had such a guarantee you
> wouldn't be using promises).

To be fair, JavaScript is already littered with hazards like this. For
example, I often find myself reading code structured like this and
wondering whether it will print "12" or "21":

addEventListener("foo", event => dump("1"));
doSomethingThatTriggersFoo();
dump("2");

...which is not to say that we should leave such hazards around if we
can avoid it. But if we can't avoid it (or don't want to for some
compelling reason), at least experienced JS programmers will be used to
dealing with the consequences. :/

Dave Camp

unread,
Apr 4, 2013, 2:59:22 PM4/4/13
to Matt Brubeck, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev

On Thu, Apr 4, 2013 at 11:57 AM, Matt Brubeck <mbru...@mozilla.com> wrote:

To be fair, JavaScript is already littered with hazards like this. For example, I often find myself reading code structured like this and wondering whether it will print "12" or "21":

addEventListener("foo", event => dump("1"));
doSomethingThatTriggersFoo();
dump("2");

...which is not to say that we should leave such hazards around if we can avoid it.  But if we can't avoid it (or don't want to for some compelling reason), at least experienced JS programmers will be used to dealing with the consequences.  :/

Sure, but for the most part you can test, see which behavior is actually happening, and mostly trust that it's not a *designed part of the feature you're using* that it might change.

-dave

Gavin Sharp

unread,
Apr 4, 2013, 3:08:08 PM4/4/13
to David Rajchenbach-Teller, Dave Townsend, Firefox Dev
On Thu, Apr 4, 2013 at 2:16 AM, David Rajchenbach-Teller
<dte...@mozilla.com> wrote:
> Actually, there is no tradeoff in your list.
> With Paolo's proposal, we can already have all of:
> - don't make measurement harder than they are;
> - don't reduce throughput (possibly actually improve it somewhat);
> - eliminate runaway stack growth in almost all cases;
> - don't make behavior any harder to predict.

dcamp's example points out why this last one isn't true. Where are the
counter-examples that demonstrate a real-life problem caused by
"reduced throughput"? In cases where performance is crucial, it seems
like we should generally be avoiding promises in favor of moving
everything into a worker, as you've argued earlier in this thread.

Gavin

Dave Townsend

unread,
Apr 4, 2013, 3:08:27 PM4/4/13
to Dave Camp, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev
On Thu, Apr 4, 2013 at 11:47 AM, Dave Camp <dc...@mozilla.com> wrote:
I think the problem some people have with non-deferred-promises is that given the following code:
---
foo().then( ){
  dump("1");
  return nextTickPromise();
}).then(function() {
  dump("2");
});

dump("3");
---

Whether you get "123" or "312" depends on the implementation of foo().  And if it's sync today, you have no guarantee that it will stay that way (like mconnor said - if you had such a guarantee you wouldn't be using promises).  With async promises you can guarantee that order will be 321.

You mean 312 right?

There are also two parts that we should be careful about conflating. One is whether calling then() on an already resolved promise will call the callbacks before then() returns. A+ says this should never happen and it alone would serve to ensure the order of events above is 312. The other part is whether resolving a promise returns before starting to call callbacks. Maybe that part is less of a concern.

Gavin Sharp

unread,
Apr 4, 2013, 3:21:09 PM4/4/13
to Paolo Amadini, Firefox Dev
On Thu, Apr 4, 2013 at 7:55 AM, Paolo Amadini <paolo....@amadzone.org> wrote:
> About the second one, I'd argue that the converse is also true: if we
> had a promise implementation that always waited a tick on resolution,
> callers might accidentally rely on that behavior as well.

Relying on a specific behavior is not a problem in and of itself. It's
only a problem if that behavior is not easily predictable, or depends
on factors outside of your control. dcamp's example suggests that
sync-resolution promises are more susceptible to those kinds of
problems.

Gavin

Richard Newman

unread,
Apr 4, 2013, 4:22:27 PM4/4/13
to Gavin Sharp, Paolo Amadini, Firefox Dev
> Relying on a specific behavior is not a problem in and of itself. It's
> only a problem if that behavior is not easily predictable, or depends
> on factors outside of your control

Or if it changes later.

The value of deferred promise resolution, above and beyond stack flattening (which seems to be a solved problem for non-deferred promises) is that it eliminates one of the two possibilities for sequencing, and thus forces callers to write safer code.

If promises resolve on the same tick *now*, then people will make their code work in that case. If some other part of the tree changes to resolve on a subsequent tick -- and this encapsulation of asynchrony is part of the value of promises -- then a distant caller could be subtly broken because of a timing inversion. In the worst case that assumption isn't even tested, and we don't know it breaks.

This is not to say that always-deferred resolution is a magic bullet for timing problems, but it should reveal more bugs than the alternative.


For clarity:

I would rather we have synchronous resolution as an option for callers who know that their calls will already involve delays due to async work, and are prepared for it, instead of providing synchronous resolution as a way to use promises while keeping linear execution. It's an optimization if you know you're safe, not an alternative resolution strategy.

Dave Camp

unread,
Apr 4, 2013, 4:27:16 PM4/4/13
to Irakli Gozalishvili, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Richard Newman, Mike Connor, Firefox Dev
Yeah.  I'm not saying getting it right is impossible (or I'd be strongly anti-sync-promises), just that getting it right requires education and discipline.  And the end result is hard to reliably test.  So it feels like a short opt-in (syncThen() or something) is a reasonable thing for someone to do if they have the right education and discipline.

Obviously my example was contrived.  If you want to dump 312 you can type dump("312").  But all the way up the callstack every caller needs to be durable against something maybe-happened-and-maybe-didn't.

(particularly if our default behavior adds a pitfall that someone that thinks they know promises from another environment can hit).

-dave


On Thu, Apr 4, 2013 at 1:12 PM, Irakli Gozalishvili <goz...@mozilla.com> wrote:
Another solution to just be extremely clear that no assumptions about call order can be made, and that code above can print either 123 or 312 and that may depend on the state of the runtime, same as timeouts don't make strong guarantees. And educate consumer agains such assumptions, by suggesting solution for both cases:

If You want to print 312 just do

dump(3)
foo().then(=> dump(1)).then(dump(2))

If you want 123 then include last bit in the chain:

foo().then(=> dump(1)).then(=> dump(2)).then(=> dump(3))


--
Irakli Gozalishvili

Irakli Gozalishvili

unread,
Apr 4, 2013, 4:12:05 PM4/4/13
to Dave Townsend, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
Another solution to just be extremely clear that no assumptions about call order can be made, and that code above can print either 123 or 312 and that may depend on the state of the runtime, same as timeouts don't make strong guarantees. And educate consumer agains such assumptions, by suggesting solution for both cases:

If You want to print 312 just do

dump(3)
foo().then(=> dump(1)).then(dump(2))

If you want 123 then include last bit in the chain:

foo().then(=> dump(1)).then(=> dump(2)).then(=> dump(3))


--
Irakli Gozalishvili

On Thursday, 2013-04-04 at 12:08 , Dave Townsend wrote:

David Rajchenbach-Teller

unread,
Apr 4, 2013, 4:34:45 PM4/4/13
to Gavin Sharp, Dave Townsend, Firefox Dev
On 4/4/13 9:08 PM, Gavin Sharp wrote:
> On Thu, Apr 4, 2013 at 2:16 AM, David Rajchenbach-Teller
> <dte...@mozilla.com> wrote:
>> Actually, there is no tradeoff in your list.
>> With Paolo's proposal, we can already have all of:
>> 1. don't make measurement harder than they are;
>> 2. don't reduce throughput (possibly actually improve it somewhat);
>> 3. eliminate runaway stack growth in almost all cases;
>> 4. don't make behavior any harder to predict.
>
> dcamp's example points out why this last one isn't true.

Indeed. It is true, even, that I have initially written code that
depends on such behaviors. However, I now avoid such hacks, because 1/
it makes way too many hypothesis on just about everything, which goes
into the way of reviewing and maintaining the code, and of moving stuff
between workers 2/ Task.jsm makes it possible to achieve the same level
of control, with much more readable code that does not depend on such
low-level implementation details.

> Where are the
> counter-examples that demonstrate a real-life problem caused by
> "reduced throughput"? In cases where performance is crucial, it seems
> like we should generally be avoiding promises in favor of moving
> everything into a worker, as you've argued earlier in this thread.

I may be misunderstanding your question, but you seem to be asking me to
demonstrate that reducing the speed of completion of an algorithm by
several order of magnitude (pending benchmarks) is a problem. Is that
correct?

Cheers,
David

--
David Rajchenbach-Teller, PhD
Performance Team, Mozilla

Gavin Sharp

unread,
Apr 4, 2013, 4:44:14 PM4/4/13
to David Rajchenbach-Teller, Dave Townsend, Firefox Dev
On Thu, Apr 4, 2013 at 1:34 PM, David Rajchenbach-Teller <dte...@mozilla.com> wrote:
I may be misunderstanding your question, but you seem to be asking me to
demonstrate that reducing the speed of completion of an algorithm by
several order of magnitude (pending benchmarks) is a problem. Is that
correct?

I understand the theory; I'm asking for an example of where the problem proves important enough to worry about in practice. In practice, time-to-completion of a computation isn't the only performance metric that matters, particularly for computations on the main thread.

Gavin

Dave Camp

unread,
Apr 4, 2013, 6:23:33 PM4/4/13
to Richard Newman, Paolo Amadini, Gavin Sharp, Firefox Dev
On Thu, Apr 4, 2013 at 1:22 PM, Richard Newman <rne...@mozilla.com> wrote:

If promises resolve on the same tick *now*, then people will make their code work in that case. If some other part of the tree changes to resolve on a subsequent tick -- and this encapsulation of asynchrony is part of the value of promises -- then a distant caller could be subtly broken because of a timing inversion. In the worst case that assumption isn't even tested, and we don't know it breaks.

Yeah, this is the essence of my concern: my concern isn't that we're asking coders to deal with nondeterministic behavior.  We deal with that all the time.  It's that we're asking them to deal with "deterministic, for now." behavior.

-dave

Gregory Szorc

unread,
Apr 4, 2013, 7:40:14 PM4/4/13
to Dave Camp, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev
Chaos monkey can't wait to test a build where the promise delay is
rand(0, 100) ticks or where the JS engine inserts random amounts of
no-op ticks onto the event loop. I know plenty of poorly written tests
today that expect events on the-next-tick-only. I suspect many of them
have intermittent oranges. If only we had the automation capacity to
periodically perform fuzz testing like this.

David Rajchenbach-Teller

unread,
Apr 5, 2013, 5:15:27 AM4/5/13
to Gavin Sharp, Dave Townsend, Firefox Dev
Well, here are a few from the top of my head:
1. startup duration;
2. shutdown duration (e.g. FHR's complex shutdown process);
3. managing to complete Sync/Picl/Session Restore snapshot operations
within a reasonable time frame;
4. time to reactivate Fennec after a context switch;
5. anything that eats lots of RAM during the operation.

Cheers,
David

>
> Gavin

David Rajchenbach-Teller

unread,
Apr 5, 2013, 5:26:14 AM4/5/13
to Gregory Szorc, Gavin Sharp, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
As project Async & Responsive (tba) will largely depend on promises, I
would like to try and wrap this up quickly.

So far, it looks like the only reason for adopting *deferred* execution
that was actually defended is that, in simple cases (arguably race
conditions), their behavior is easier to predict syntactically. As far
as I am concerned, there is no good reason to rely on a specific
resolution of race conditions – doubly so since Task.js makes it trivial
to avoid the race conditions in all the meaningful cases that I can
imagine. As pointed out half-jokingly by Gps, we can even introduce
fuzz-testing to root out some race conditions. That might actually be
simple.

So, here is my proposal:
1. promises remain in add-on SDK, with the process outlined by Mossop;
2. get Paolo's implementation of promises finished and landed, confirm
that it solves the issues we have with stack explosion;
3. progressively (certainly quickly) add support for debugging options;
4. if we believe that developers will depend on subtleties of evaluation
order, work on fuzz-testing promises;
5. provide a simple wrapper to defer execution of a promise.

Best regards,
David

--
David Rajchenbach-Teller, PhD
Performance Team, Mozilla

Mike de Boer

unread,
Apr 5, 2013, 7:19:41 AM4/5/13
to David Rajchenbach-Teller, Gavin Sharp, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev, Dave Townsend
Hi all,

As some of you might now already, I'm not a fan of Promise libraries and the wrappers that (might) come with it or will be developed in the future - during my talks with people during my short time of a month here at Mozilla.
The gist at https://gist.github.com/mikedeboer/5305020 explains the first ten reasons why, but these are certainly not the only ones, jut the first ten reasons.

My concerns are not at all with the concept and A+ specification, but more with how the libraries that implement them are used and more often abused in code that I've had the pleasure to read and maintain the past couple of years. In this regard my concern also goes out to project tba and its maintainability in the coming years, as that will be its impact.

When being faced with a major refactor that involves 'asyncifying' a considerable number of APIs - I've been there on numerous occasions with teams I have lead - I always urged developers to take a step back and not focus on trying to marry asynchronous code flows with synchronous ones, but instead focus on how the goal can be accomplished with the language features at hand. In the case of Javscript, `yield` semantics did not coincide with co-routines, thus the need for helper libraries arose pretty quickly.
If a projects' goal is to keep its iteration in sync with the pace of innovation of language design, than it is of great importance to keep the level of abstraction as lean as possible. In practice this meant that using a Promise library moves away too much from the structure of language.

Another positive side-effect of this focus was that asynchronous code looked substantially different from synchronous code. In practice this meant that the mental model of the engine the code is running on top of takes notice of code running in an event loop,  out-of-process or in multiple threads. Continuous awareness of this fact obviously reduces the chance of introducing race-conditions.

In the case of JS, Iterators/ generators, first-class lambdas and arrow syntax (and more) all will greatly reduce the amount of code required for asynchronous flows. A simple convention to reserve the first argument to a callback for errors has the positive side-effect that errors are never discarded or 'forgotten' and always bubble up to the callee.

If it matters, my background is that of working on enterprise-scale NodeJS backends for over two years at Cloud9 IDE and in-browser JS for over eight years. For some reason, Promise libraries never caught on in the NodeJS ecosystem, I hope you now understand why.

I am pretty sure this ship has sailed already, in which case my story will just serve as a big fat 'I told you so' in two years or so from now :) And that's perfectly fine!

Have fun!

Mike.

Panos Astithas

unread,
Apr 5, 2013, 8:53:20 AM4/5/13
to Mike de Boer, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev, Dave Townsend
I'd like to second the arguments made so far about helping others not intimate with the semantics of this discussion use promises in a less error-prone way. In general POLA violations can and will annoy developers, especially those who encounter Firefox code for the first time, coming from another community that uses a particular flavor of promises.

The initial goal of the add-on SDK promise implementation was to use the Q API, from which I assert it should follow that the Q async style should be used as well:

http://documentup.com/kriskowal/q/#tutorial

Mark Miller argued convincingly IMHO about async being the better style, in the Q mailing list and in a position paper co-authored with Tom Van Cutsem:

https://groups.google.com/forum/#!msg/q-continuum/dfH0Mlognys/1xJE3hkZiocJ
http://static.googleusercontent.com/external_content/untrusted_dlcp/research.google.com/en/us/pubs/archive/37626.pdf

Section 3 and Table 1 in that paper are worth reading.

Furthermore, the Promises/A+ spec that we seem to be considering as the most important one to follow, explicitly specifies async in section 3.2.4:

http://promises-aplus.github.com/promises-spec/

The arguments that went back and forth before reaching consensus on that are worth reading, too:

https://github.com/promises-aplus/promises-spec/issues/4

One could argue that we should be skating to where the puck is going to be, and DOM Futures seem like the future (pun intended, but also unavoidable). It isn't entirely obvious to me from the current spec if they support async only or both, because I'm having a hard time parsing the specese around the "synchronous flag":

http://dom.spec.whatwg.org/#futures

But from other stuff I've read it seems that the expected behavior is async:

https://github.com/slightlyoff/DOMFuture/issues/47
https://github.com/slightlyoff/DOMFuture/blob/master/DOMFuture.idl#L72-79

So, in summary, I think it's clear that we need both sync and async versions of the API, but being async by default seems to me like it strikes the right balance between allowing expert hackers writing performance-critical code to use promises, while at the same time being familiar to developers coming from Q, CommonJS, or the DOM spec, or just using promises after Googling for instructions.


Cheers,
Panos

P.S.: not that I ever google an API before using it, god forbid! Who does that anyway?

Dave Camp

unread,
Apr 5, 2013, 10:46:09 AM4/5/13
to David Rajchenbach-Teller, Gavin Sharp, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Mike Connor, Firefox Dev, Gregory Szorc
I talked a bit with Yoric on irc, and I hope this is a reasonable summary of the discussion:

* It's a not-uncommon pattern for an API implementor to add a then() or two to the chain to fixup return values of other functions (at least Yoric and I have both done that)
* Deferring these sorts of fixups to another tick is pretty wasteful.
* The problems I've been describing really apply to returning an already-resolved promise from an API entry point - synchronous resolution of then() chains seems right to me.
* Asking every consumer of a promises API to be resilient against already-resolved return values seems unreasonable to me.
* Asking every producer of promises to think about this by default seems unreasonable.

So it seems like Promise.defer() should (as its name implies) return a promise that can't be resolved on the same tick, and we can add a footgun to return a promise that can be resolved on the same tick.
then() would remain sync.

This moves the burden of keeping a consistent API (that isn't returning already-resolved promises) to the API producer rather than the promises library, but makes it happen by default and prevents ticking on every then().

-dave (who will probably stop talking about promises now).

Gavin Sharp

unread,
Apr 5, 2013, 12:32:26 PM4/5/13
to David Rajchenbach-Teller, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev, Dave Townsend
I don't think I quite agree with your characterization of "the only
reason", but I don't have any objections to your plan of action. I
think item 5 is important, which I think matches with Dave's latest
suggestion as well.

I think we've discussed this enough, so let's focus on completing
items 2, 3 and 5 in your list.

Gavin

Dave Townsend

unread,
Apr 5, 2013, 1:26:20 PM4/5/13
to Panos Astithas, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Mike de Boer, Firefox Dev
On Fri, Apr 5, 2013 at 5:53 AM, Panos Astithas <pa...@mozilla.com> wrote:
One could argue that we should be skating to where the puck is going to be, and DOM Futures seem like the future (pun intended, but also unavoidable). It isn't entirely obvious to me from the current spec if they support async only or both, because I'm having a hard time parsing the specese around the "synchronous flag":

http://dom.spec.whatwg.org/#futures

The DOM futures spec is very clear that callbacks passed to then() cannot be called in the current event tick. This matches the A+ spec precisely. But yeah it's totally unclear whether calls to resolve start calling callbacks synchronously or not.

Gregory Szorc

unread,
Apr 5, 2013, 1:26:51 PM4/5/13
to Mike de Boer, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
On 4/5/2013 4:19 AM, Mike de Boer wrote:
In the case of JS, Iterators/ generators, first-class lambdas and arrow syntax (and more) all will greatly reduce the amount of code required for asynchronous flows. A simple convention to reserve the first argument to a callback for errors has the positive side-effect that errors are never discarded or 'forgotten' and always bubble up to the callee.

If it matters, my background is that of working on enterprise-scale NodeJS backends for over two years at Cloud9 IDE and in-browser JS for over eight years. For some reason, Promise libraries never caught on in the NodeJS ecosystem, I hope you now understand why.


As I've said many times, I don't believe promises by themselves provide a *compelling* advantage over standard callbacks. About the only benefit they provide to consumers is the ability to chain a single error handler to a series of async function calls without "buy-in" from all the intermediate APIs. In my mind, the real benefit to promises is Task.jsm. e.g. https://hg.mozilla.org/mozilla-central/file/55f9e3e3dae7/services/metrics/storage.jsm#l652. If Task.jsm didn't exist, I likely wouldn't be using promises to the same liberal extent I now am.

If others have the same opinion, then it's quite possible that promises never caught on in Node because V8 does not have the language support for generators required to support tasks! Only recently did they signal they are working on implementing generators (https://code.google.com/p/v8/issues/detail?id=2355).

Both a benefit and pitfall of working on JS inside Firefox is we often have access to JS language features before the broader JS developer community (because SpiderMonkey is often the first implementation of new language features). As such, I expect us to be at the forefront� of discovering new and exciting practices now possible with new language features. Tasks (via promises) are one such feature. I think we should capitalize on this opportunity (while exercising prudence over the use of bleeding-edge features, of course).

Irakli Gozalishvili

unread,
Apr 5, 2013, 1:51:15 PM4/5/13
to Gregory Szorc, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Richard Newman, Dave Camp, Mike Connor, Mike de Boer, Firefox Dev
Little off topic here (in reply to comments people have made):

Promises can provide certain advantages if you structure programs in a certain way, which can also be seen as
disadvantage as they imply certain style. Weather they are useful or not for solving specific problem depends on
the problem, discarding them in general feels like a rush decision.

There were also interesting debates on internet about promises & article explaining advantages of them (which are
not chaining) I'd really recommend reading it unless you've already done that:

There is also proposal for TC39 for adding promises to JS:

--
Irakli Gozalishvili

On Friday, 2013-04-05 at 10:26 , Gregory Szorc wrote:

On 4/5/2013 4:19 AM, Mike de Boer wrote:
In the case of JS, Iterators/ generators, first-class lambdas and arrow syntax (and more) all will greatly reduce the amount of code required for asynchronous flows. A simple convention to reserve the first argument to a callback for errors has the positive side-effect that errors are never discarded or 'forgotten' and always bubble up to the callee.

If it matters, my background is that of working on enterprise-scale NodeJS backends for over two years at Cloud9 IDE and in-browser JS for over eight years. For some reason, Promise libraries never caught on in the NodeJS ecosystem, I hope you now understand why.


As I've said many times, I don't believe promises by themselves provide a *compelling* advantage over standard callbacks. About the only benefit they provide to consumers is the ability to chain a single error handler to a series of async function calls without "buy-in" from all the intermediate APIs. In my mind, the real benefit to promises is Task.jsm. e.g. https://hg.mozilla.org/mozilla-central/file/55f9e3e3dae7/services/metrics/storage.jsm#l652. If Task.jsm didn't exist, I likely wouldn't be using promises to the same liberal extent I now am.

If others have the same opinion, then it's quite possible that promises never caught on in Node because V8 does not have the language support for generators required to support tasks! Only recently did they signal they are working on implementing generators (https://code.google.com/p/v8/issues/detail?id=2355).

Both a benefit and pitfall of working on JS inside Firefox is we often have access to JS language features before the broader JS developer community (because SpiderMonkey is often the first implementation of new language features). As such, I expect us to be at the forefront  of discovering new and exciting practices now possible with new language features. Tasks (via promises) are one such feature. I think we should capitalize on this opportunity (while exercising prudence over the use of bleeding-edge features, of course).

Irakli Gozalishvili

unread,
Apr 5, 2013, 1:53:05 PM4/5/13
to Gregory Szorc, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Richard Newman, Dave Camp, Mike Connor, Mike de Boer, Firefox Dev
I also wonder how jQuery goes about sync / async resolution, I'd expect more people to be familiar with jQuery than Q.

--
Irakli Gozalishvili

Dave Townsend

unread,
Apr 5, 2013, 2:08:29 PM4/5/13
to Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
Yep I agree with this, let's move forwards!

Irakli Gozalishvili

unread,
Apr 5, 2013, 2:11:11 PM4/5/13
to Firefox Dev, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Richard Newman, Dave Camp, Mike Connor, Mike de Boer, Gregory Szorc
My quick tests with jQuery deferreds indicate that they resolve and invoke handlers synchronously

var result = null
var d = $.Deferred()
d.promise().then(function() { result = "async" })
result = "sync"

setTimeout(function() { console.log("jQuery promises are " + result) })

[10:59:11.043] jQuery promises are sync

I suspect for the same reasons to why add-on SDK chose to not follow Q's footsteps here:

Just thinking out loud: Callbacks do no guarantees weather they are called sync or async, such guarantees
can be made by users choosing to decorate cases where they require them. It's unclear to me why promises
would try to solve imply certain decisions there, rather than let consumer decide.

function deferred(f) {
  return function(args...) {
    let { promise, resolve, reject } = defer();
    setTimout(function(self) {
      try { resolve(f.apply(self, args)) }
      catch (e) { reject(e) }
    }, 0, this)
    return promise
}

dosomething.then(doMore).then(deferred(function() {
  dump(1)
})

dump(2)

// => 21

--
Irakli Gozalishvili

Irakli Gozalishvili

unread,
Apr 5, 2013, 2:13:47 PM4/5/13
to Firefox Dev, Gavin Sharp, David Rajchenbach-Teller, Paolo Amadini, Dave Townsend, Richard Newman, Dave Camp, Mike Connor, Mike de Boer, Gregory Szorc
Either way I'm fine with current plan as long as SDK will be able to opt-out from specific decisions by wrapping
core promise implementation and preserve it's current behaviour which is sync and no warnings by default. 

--
Irakli Gozalishvili

David Rajchenbach-Teller

unread,
Apr 5, 2013, 3:58:17 PM4/5/13
to Gavin Sharp, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev, Dave Townsend
We discussed over IRC the matter of deferring execution with a subset of
#devtools. Let me suggested an amended plan that should, I hope, please
everybody.

Plans for Promise, v2
1. Promise remains in add-on SDK.
2. Updates to promise follow the process outlined by Mossop.
3. Execution of resolve()/reject() is deferred by default. Execution of
then() isn't.
4. Get Paolo's implementation updated to take into account previous
point, finished, tested, reviewed and landed to the SDK asap.
5. Once it has landed, progressively add support for debugging options,
as needed. Bonus points if these options can be implemented as a wrapper.
6. If necessary, add support for deferring execution of then(),
preferably as a wrapper.
7. (optionally) If some chaos monkey is motivated, work on fuzz-testing.
Again, as a wrapper.

Just in case this discussion continues, I intend to put the latest plans
here:
https://etherpad.mozilla.org/Async-Responsive

Asynchronously yours,
David

P.S.: Gavin, please don't forget the end of the sentence: I mentioned
that it was the only reason *that was actually defended* :)


On 4/5/13 6:32 PM, Gavin Sharp wrote:
> I don't think I quite agree with your characterization of "the only
> reason", but I don't have any objections to your plan of action. I
> think item 5 is important, which I think matches with Dave's latest
> suggestion as well.
>
> I think we've discussed this enough, so let's focus on completing
> items 2, 3 and 5 in your list.
>
> Gavin

Paolo Amadini

unread,
Apr 5, 2013, 4:17:11 PM4/5/13
to David Rajchenbach-Teller, Gavin Sharp, Gregory Szorc, Dave Townsend, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
On 05/04/2013 21.58, David Rajchenbach-Teller wrote:
> Plans for Promise, v2
> 3. Execution of resolve()/reject() is deferred by default. Execution of
> then() isn't.
> 4. Get Paolo's implementation updated to take into account previous
> point, finished, tested, reviewed and landed to the SDK asap.

I've read the discussion behind the choice of requesting delayed
resolution of promises in the Promises/A+ proposal (thanks for the
link!), and I understand the kind of consistency that's being sought:

https://github.com/promises-aplus/promises-spec/issues/4#issuecomment-9778686

Actually, it's quite trivial to add a guarantee to that effect to the
draft implementation I already submitted, delaying by one tick when
necessary and resolving within the same tick while we are already
processing a chain of promises, without returning to the operating
system's event loop (which is where the performance concern lies).

I'll refine the implementation and submit it for review early next week.

Cheers,
Paolo

Dave Townsend

unread,
Apr 5, 2013, 6:13:42 PM4/5/13
to David Rajchenbach-Teller, Gavin Sharp, Paolo Amadini, Gregory Szorc, Irakli Gozalishvili, Richard Newman, Dave Camp, Mike Connor, Firefox Dev
I approve
Reply all
Reply to author
Forward
0 new messages