Killing `Promise.fulfill`

Mark S. Miller erights at
Tue Aug 20 09:18:14 PDT 2013

On Tue, Aug 20, 2013 at 8:32 AM, Tab Atkins Jr. <jackalmage at>wrote:

> On Tue, Aug 20, 2013 at 7:08 AM, Mark S. Miller <erights at>
> wrote:
> > Hi Anne, Thanks for the reminder. My message of last night fell into that
> > same old trap (ignoring the storage cost) and that previous reversal of
> mine
> > is still mostly correct, However, the missing operation is not .fulfill
> for
> > the reasons Domenic correctly explains. It is .accept (possibly named
> .of)
> > because it observably differs from .resolve only to .flatMap observers;
> > whereas the distinction between "pending", "fulfilled" and "rejected" is
> > observable to .then observers.
> I have no idea what happened in this thread, because people keep
> referring to "the earlier X" where by "earlier" they mean "in another
> unnamed thread, days/weeks ago" rather than just enumerating what
> they're talking about.  (This kind of thing is acceptable while the
> other threads are ongoing in parallel; it's not when the threads have
> been paged out of people's heads.)
> But in this paragraph, specifically, what are you talking about, Mark?

See <>,
which is the message Anne links to in the message I'm immediately replying

But yes, I agree that we've all been using terms like "earlier" too
loosely, where we should be more explicit about context.

>  What is this "missing operation",


> who needs it, and why?

To answer this precisely, we need good terminology to distinguish two
levels of abstraction: The distinctions observable to the AP2.flatMap
programmer and the coarser distinctions observable to the AP2.then
programmer. Let's start ignoring thenables and considering only
promises-vs-non-promises. Let's also start by ignoring rejection.

At the AP2.flatMap level,
  * for a promise p and an arbitrary value v, p may accept v. p is then in
the "accepted" state.
  * for a promise p and a promise q, p may adopt q. p is then in the
"adopting" state.
Putting these together, we can also say
  * for a promise p and an arbitrary value v, p is resolved to v if p
either accepts v or adopts v. p is then in the "resolved" state.

p2 = p1.flatMap(v1 => q2)

means, if p1 is accepted, then v1 will be what it has accepted.

If q2 is a promise, then p2 adopts q2.

p2.flatMap(...) fires as a result of acceptance but not adoption. If q2
accepts, then p2 likewise accepts and p2.flatMap fires by virtue of this

At the P2.then level
  * for a promise p and a non-promise v, p may be fulfilled with v. p is
then in the fulfulled state.
  * for a promise p and a promise q, p may follow q. p is then in the
following state.
  * Until a promise p is either fulfilled or rejected, it is pending.
Putting these together, we can also say
  * for a promise p and an arbitrary value v, p is resolved to v if either
p is fulfilled with v or p follows v. p is then in the "resolved" state.

p4 = p3.then(v3 => v4)

means, if p3 is fulfilled, then v3 will be what p3 is fulfilled with.

p4 is resolved to v4. If v4 is a promise, then p4 follows v4. Else p4 is
fulfilled with v4.

p4.then fires as a result of fulfillment but not following. If p4 follows
v4 and v4 fulfills, then p4 likewise fulfills and p4.then fires by virtue
of this fulfillment.

Notice that "resolved" is the same states at each level, even though these
states are described differently. That is why we can use the same term at
both levels. Likewise, the concept of "unresolved" is meaningful at both

> From what I understood talking to Domenic, here's what we needed:
> 1. Promise.resolve() and Promise.reject().  These just take a value
> and wrap it in a Promise, putting it on the success or failure track.

I'm totally confused by your description of reject, but let's leave it
aside for now as I have above.

I'm not so much concerned with the static .resolve method, since the extra
storage cost for the static method is negligible. However, what does
aResolve.resolve do? If it causes its promise to accept, this must be
observably different to .flatMap observers than if it causes its promise to
adopt. This difference is not observable to .then observers, which is why
I've accidentally missed this issue twice now. But since an implementation
cannot know ahead of time whether there might be .flatMap observers, using
.accept for .resolve would impose prohibitive storage costs on .then
oriented patterns. See the message Anne linked to.

> 2. Promise#then() and Promise#flatMap().  flatMap waits for pending
> promises to become resolved or rejected,

If p adopts q and q is unresolved, then p is resolved but p.flatMap does
not fire.

> and calls its callbacks
> accordingly; then() waits for pending or resolved promises to become
> fulfilled or rejected, and calls its callbacks accordingly.
> 3. The resolved value of a promise (what's passed to the success
> callback of flatMap()) is simply the value in the promise.

True for acceptance, not for adoption.

>  The
> fulfilled value of a promise (what's passed to the success callback of
> then()) depends on what's inside the promise: if it's a plain value,
> that's the fulfilled value; if it's a promise, its that promise's
> fulfilled value.
> Do you think we need anything else?  If so, why, and for what purpose?

Only to avoid the prohibitive storage cost of using .accept to do the job
of .resolve. Storage aside, there is no semantic need to distinguish these

> (Note that if anyone thinks we need something that eagerly flattens a
> promise, rather than flattening happening implicitly via the
> definition of "fulfilled value" for then(), realize that this eager
> flattening operation is hostile to lazy promises.  While this might be
> *useful* in some cases, it's probably not something we need or want in
> the core language, or if we do, it should be given an appropriately
> descriptive name, rather than yet another synonym for "accept".)

I agree that we do not need eager flattening.

> ~TJ

-------------- next part --------------
An HTML attachment was scrubbed...
URL: <>

More information about the es-discuss mailing list