Thanks to Joe and yourself for getting this started.
I believe that we rather want
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;
> }
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.
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
Sounds good.
> 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
I can think of a few cases in which you want to do this. However, with
> 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
my proposal above, we can simply mark this as normal/unexpected on a
per-promise-chain basis.
My main concern with an automatically asynchronous implementation is
> 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.
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.
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).
I'm sure that we can have an option that lets us capture stacks even in
> 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.
asynchronous mode.
I have some issues with promise living in SDK, for quite a few reasons:
> 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.
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.
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.
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
> And so we have:
>
> http://hg.mozilla.org/mozilla-central/file/default/services/common/utils.js#l112
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?
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. :/
dump("1");I think the problem some people have with non-deferred-promises is that given the following code:foo().then( ){
---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.
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 dodump(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))
On Thursday, 2013-04-04 at 12:08 , Dave Townsend 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?
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.
I think we've discussed this enough, so let's focus on completing
items 2, 3 and 5 in your list.
Gavin
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
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.
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).