Promises: final steps
I'm fine with the subset defined in Domenic's spec - it's compatible with what we've discussed, and is easy to extend into the flatMap semantics I want.
I've commented on the issue threads in github.
This looks like good work. I like the name "cast" in particular, as I can imagine a future casting operator which provides sugar for the cast function defined on a constructor.
The only concern I have is over error-swallowing. What's the approach for this minimal API?
On Wed, Sep 4, 2013 at 7:34 AM, Kevin Smith <zenparsing at gmail.com> wrote:
The only concern I have is over error-swallowing. What's the approach for this minimal API?
As far as I know, the current plan is still that devtools should handle swallowed exceptions, since we got rid of .done() some time ago. Plans may have changed without me knowing, though!
I'd be interested in more detail since I think this will be an important usability issue. Are any of the various devtool efforts currently implementing or planning such a feature?
My colleagues and I are working on an extension for Chrome Web Inspector that can communicate with promise libraries, particularly Q, over the window message port. The tool, which will be renamed and rewritten before it is ready for general use, adds a Promises tab to Web Inspector that shows all currently pending and unhandled asynchronous errors, as well as stack traces for both, and also progress information, albeit determinate, indeterminate, or indeterminate but lively. There is a video accompanying for demonstration.
The promise client broadcasts when a promise is deferred, when a deferred
promise is resolved, when a deferred promise makes progress (through the
deferred.notify
interface), when a fulfilled promise is created, when a
rejected promise is created, and when a rejection is handled. As such, the
inspector can reconstruct whatever portion of the program’s promise history
it elects to retain.
In time, I intend to formalize a protocol. Ideally this system would be useful for both “primordial” and library promises, and combinations of both. Of course, any assistance would be valuable.
Also, ideally this would approach the functionality available to Causeway and perhaps even become a manifestation of Causeway.
It would certainly be possible to show promises in multiple contexts, including cross-origin iframes, and even show time sequence / Stevens graphs for message passing between promises in multiple JavaScript contexts, when promises are used as proxies for remote objects through a facility like Q-Connection.
While I can agree that "monitor" feature (that's proposed instead of done
)
has some benefits, I see it only as an aid for developers that are
inexperienced with promises, or as a fallback for those experienced.
It looks more as a smart add-on, which to be complete can't be implemented
in plain JavaScript, is implementation specific and should be treated as
optional.
What's more important it still doesn't provide developer with full control
of error handling on JavaScript level and that can be achieved only with
done
.
I have problems understanding why such complex and not natural feature is favored over something so simple and straightforward.
Thanks for posting this. I am also of the opinions that this fancy developer tooling -- which I think is great and desperately needed anyway -- will not substitute for the utility of .done. I expect we will eventually decide to add .done back in. It expresses programmer intent in a way that developer tooling cannot otherwise recover.
However, since we do not yet have experience with this tooling it is hard to say. Given the purpose of this repository -- a minimal-ish compatible subset of expected standard ES7 promises, adequate for DOM's immediate needs -- we should leave .done out of this subset. Just as we are leaving out many other features we expect will eventually be part of ES7 standard promises.
Thanks for the details! This gives us an idea what a "promise monitoring" feature might look like in a browser's developer tools. I think such a feature would be really cool, but I believe that promise-using programs ought to be debuggable using just a console. Indeed, for a non-GUI embedding like Node, they must be debuggable using just a console.
I don't think we should ship an API that is not debuggable using a console.
However, I'm not in favor of a done
method on the Promise prototype
because of functional overlap with then
.
Another option is a static method which takes a promise and throws rejections ala done:
Promise.throw(makeSomePromise.then(...));
Personally, I consider it a shame that promise libraries punted on the distinction between rejections and program errors, but I suppose it's too late to go there.
From: Kevin Smith [zenparsing at gmail.com]
Indeed, for a non-GUI embedding like Node, they must be debuggable using just a console.
This is an important point. A provisional idea that preserves our desire to not introduce new features to promises themselves, requiring user choice at authoring time, might be some kind of console.unhandledRejections()
function which returns you a snapshot of the current unhandled rejections bucket.
Another option is a static method which takes a promise and throws rejections ala done:
Promise.throw(makeSomePromise.then(...));
I find these kind of things confusing. RSVP did something similar, introducing
RSVP.rethrow = r => setImmediate(() => throw r);
so that you write
somePromise.then(...).catch(RSVP.rethrow); // actually RSVP uses `fail`.
It's not clear to me why this, or your Promise.throw
, is better than
somePromise.done(...)
// or
somePromise.then(...).done()
On Thu, Sep 5, 2013 at 12:04 PM, Domenic Denicola < domenic at domenicdenicola.com> wrote:
From: Kevin Smith [zenparsing at gmail.com]
Indeed, for a non-GUI embedding like Node, they must be debuggable using just a console.
This is an important point. A provisional idea that preserves our desire to not introduce new features to promises themselves, requiring user choice at authoring time, might be some kind of
console.unhandledRejections()
function which returns you a snapshot of the current unhandled rejections bucket.
That would be a global communications channel.
Another option is a static method which takes a promise and throws rejections ala done:
Promise.throw(makeSomePromise.then(...));
I find these kind of things confusing. RSVP did something similar, introducing
RSVP.rethrow = r => setImmediate(() => throw r);
so that you write
somePromise.then(...).catch(RSVP.rethrow); // actually RSVP uses `fail`.
It's not clear to me why this, or your
Promise.throw
, is better than
somePromise.done(...) // or somePromise.then(...).done()
As I said, I think we will eventually add something that provides this or similar functionality. But it does not need to be added to the subset this repository seeks to define -- for the immediate needs of DOM. Since the topic of this thread is "final steps", I'm just trying to be clear in this context that this .done-ish issue is after these "final steps".
This is the kind of issue that is best decided after more experience with several of these debugging aids, in both browser and server. So we should of course continue to discuss these options on es-discuss regarding ES7. Also, we won't have Weak References until ES7, and that bears on this discussion and expected experience. FWIW, so far, I still like .done better than the suggested alternatives.
It's not clear to me why this, or your
Promise.throw
, is better than
somePromise.done(...) // or somePromise.then(...).done()
Not much better, I'd say, but IMO a done
method which accepts a
callback overlaps too much with then
, and a done
method without a
callback just looks like a wart in need of removal. : )
Visually, to me, wrapping beats capping. But that's just me.
In the end, I like neither wrapping nor capping. In my own work I've preferred to define conditions under which certain rejections are interpreted as program errors, thereby avoiding the wrapping/capping issue entirely. (I can elaborate if anyone's interested.)
On Thu, Sep 5, 2013 at 1:21 PM, Kevin Smith <zenparsing at gmail.com> wrote:
In the end, I like neither wrapping nor capping. In my own work I've preferred to define conditions under which certain rejections are interpreted as program errors, thereby avoiding the wrapping/capping issue entirely. (I can elaborate if anyone's interested.)
Please do.
Tasks in C# throw the recorded exception when the Task is finalized by the GC if it hasn't been handled by user code, though I don't know if something similar could be supported for ES7 Promises nor whether or not that makes sense for ES7 promises either.
Having Promise rejections hold on to unhandled rejections can be mitigated either with eventual language support around Promises (either an async/await-style operator, or the ! operator in the strawman) or through a userland approach by way of trampoline functions. In the C# world with async/await, exceptions are thrown at the site of the "await" keyword when the operand becomes Faulted. In general this alleviates many of the issues around Tasks swallowing exceptions, although there are still a few cases where you have to take care around exception handling (e.g. an async method that returns void
rather than Task
or Task<T>
, as it cannot be awaited).
Promises in the short-term may have some deficiencies until there is a syntax defined for asynchronous functions. That said, I'm fine with not having Promise#done
, as useful as it is, as long as there is a reliable and well defined mechanism to raise the rejection to the host if needed (outside of reaching out to setImmediate to throw the exception, as it may not be obvious to consumers of the Promise API).
On Thu, Sep 5, 2013 at 10:32 AM, Ron Buckton <rbuckton at chronicles.org>wrote:
Tasks in C# throw the recorded exception when the Task is finalized by the GC if it hasn't been handled by user code, though I don't know if something similar could be supported for ES7 Promises nor whether or not that makes sense for ES7 promises either.
Mark alludes to this when he mentions WeakRef, which provides notifications when an object is collected. It is a missing piece in the puzzle and part of the argument for tabling the issue for a future effort.
Please do.
You're not going to like it, which will explain my hesitation : )
Think of all promises as existing in a forest, where "root" promises are
created using the Promise constructor and "child" promises are created
using then
. Promises with [[Value]] set are green. Promises with
[[Reason]] set are red. A "checkpoint" occurs immediately after the
microtask queue has been completely flushed. At any checkpoint, any
non-root leaf nodes colored red are program errors.
This forces the programmer (me, really) to either write an error handler at
the end of a then
chain, or to return the chain to a caller that will.
It also means that I must be strict about when error handlers are attached
to a chain. There may be important use cases that this strategy precludes
which I haven't yet considered...
(Added back the other lists.)
On Fri, Sep 6, 2013 at 3:58 AM, Brendan Eich <brendan at secure.meer.net> wrote:
Let's put done back in. It's the right thing.
Given what has been said thus far domenic/promises-unwrapping#19 my inclination is still to leave it out initially and give a version without done() six months to a year to mature. Not having done() can make promises harder to debug in the short term, but adding done() is trivial to do later. And given the lack of native promise implementations to date there's no way for us to test the done()-less design without trying it first.
I have not read everything about the promise/future/re-promise subject but what I have read seems to show that everyone has a personal understanding of the thing.
So please see lists.w3.org/Archives/Public/public-webcrypto/2013Sep/0003.html , code example that I have written for WebCrypto (ie real working case not using WebCrypto rewritten with WebCrypto promises), as explained I am using 'done' despite of the fact that it might be removed, because I don't see why I should use 'then' if I am not chaining anything.
As explained again, the example shows maybe that promises here are a kind of artifice, until other APIs implement promises.
How should I write this without 'done'?
Aymeric
Le 08/09/2013 19:06, Anne van Kesteren a écrit :
As many of you hopefully know, we're trying to nail down the design of promises in JavaScript so we can declare consensus on it and start shipping it in implementations. If you're interested in the particulars I strongly recommend reading through domenic/promises-unwrapping/blob/master/README.md and partaking in the domenic/promises-unwrapping/issues discussion. The next TC39 is coming close and this really needs to be resolved (not settled!) by then as there are many APIs relying on promises now.
Eg in domenic/promises-unwrapping#8 we decided on Promise.cast() as IsPromise(x) ? x : Promise.resolve(x).
domenic/promises-unwrapping#18 suggests adding Promise.prototype.finally() and domenic/promises-unwrapping#13 discusses which convenience methods we should add in the first iteration.
I suggest we focus on the minimal subset that works (which I know is different for people, but let's aim for consensus) and then iterate again after we have a couple of implementations out there.