Promises Consensus

Tab Atkins Jr. jackalmage at gmail.com
Wed Jul 31 11:23:17 PDT 2013


[Gah, resending because I'm being *way* too loose with my terminology.
 Ignore previous email - this one has identical content, but uses
terms correctly.] (Scratch that, I added a new point #3 at the end of
the email.)

[For the purposes of this email, a promise "accepting" or "rejecting"
means that its resolver's accept() or reject() method was called, or
the equivalent internal magic.  "fulfill" means "accept or reject".
"resolve" means "adopt or accept, depending on whether the value is a
promise-like or not" (in other words, what the resolver's resolve()
method does).  "adopt" means accepting or rejecting with the same
value as the adopted promise.  If I should be using better terms, let
me know.]

Heya!  I, Mark, and others have been hashing out our remaining
differences on Promises privately, and are all happy with each other
now, with only two remaining issues to be decided in a larger
audience.  Anne says that we should be able to get DOM Promises on
track with this consensus if we finish up the discussion in the next
month or so, since the differences from the current spec are mostly
internal/new API.

Here's our current consensus:

Promises have both a .then() and a .flatMap() method.

1. p.flatMap() does "single-level" resolution:
    * whatever p fulfills to, gets passed to the flatMap() callbacks.
    * The callback return value *must* be a promise-like, which is
adopted by the output promise; otherwise, the output promise rejects
with a TypeError.

2. p.then() does "recursive" resolution on the input side (per
consensus following 2 TC39-meetings ago):
    * if p accepts to a promise-like, the .then() callbacks get moved
down to that promise-like until it either accepts with a
non-promise-like, or rejects.
    * Rejection calls the rejection callback without delay; no extra
resolution mechanics happen here.
    * The callback return value can be a promise-like or not.  If it
is, the output promise adopts it; if not, the output promise accepts
it.

3. The helper functions (Promise.every(), etc.) use .then() semantics.
That is, Promise.every() will eventually accept to an array of
non-promise-likes.


The first issue still up for community discussion involves the
definition of "promise-like".

We'd like the definition to be: (a) a Promise or subtype, or (b) a
branded non-Promise (with the branding done via Symbol or similar).
Promises/A+ wants the branding to be done via a method named "then"
(the "thenable" concept).

This, unfortunately, goes directly against TC39 practices in a number
of other areas, such as iterators, where we don't want short string
names as branding due to the possibility of collision.  (In the case
of "then", collision isn't a possibility, it's a certainty - we *know*
there are libraries out there today that put a "then" method on their
objects without referring to Promises.)  Thoughts?


The second issue still up for community discussion is what "adopts"
means, precisely.

1. Assume a .then() callback returns a non-native promise-like.  We
can't just use magic internal operations to detect when the returned
promise fulfills, so the output promise will have to register
callbacks on it.  This appears to break our desire to have "lazy"
promises in the future that don't compute a value until someone asks
for it. Should we specify that adoption is done late?  (That is, the
output promise would hold onto the returned promise without touching
it, until someone actually registers some callbacks on it.)  This may
have performance implications - is it possible that we just do eager
resolution now, but later have detection for lazy promises getting
returned and switch to lazy behavior in just those cases?

2. Assume a .flatMap() callback returns a non-native promise-like.
Obviously, the output promise adopts it by registering .flatMap()
callbacks on it.  But what if the promise-like only has a .then()
method?  Should we reject with a TypeError, or fall back to using
.then() resolution semantics?  (I suspect we need to do the former to
maintain monad laws.)

3. For that matter, what about adopting the returned promise value of
a .then() callback?  If you try and use .then() to listen for the
returned promise to fulfill, you'll end up imposing full recursive
semantics on the output promise, regardless of whether they're
observed with .flatMap() or .then().  Looks like we should default to
trying to adopt with .flatMap(), and then maybe fall back to .then()
for .then()-returned promise-likes.

~TJ


More information about the es-discuss mailing list