AP2 makes laziness easy.

# Mark S. Miller (12 years ago)

I was recently asked:

(Forwarded with permission)

I was wondering if I could get your thoughts on delaying request execution until invocation of 'then.' [...] I don't always want an HttpRequest object to make a network request on creation, because sometimes I merely aggregate the HttpRequest objects into a single HttpBatch object that I can execute.

Do you see any problems with offering an alias like this?

HttpRequest.protototype.then = function(onFul, OnRej) { return this.execute().then(onFul, OnRej); };

[...] What would be the implications of this? [...]

Two things struck me about this questions:

First:

The move to AP2, where assimilation happens only on the input side of a .then, makes thenables such as those above a perfect way to express laziness. In current Promises/A+, where assimilation happens on the output side of a .then, the thenable above will be "forced" too early:

// hr is the thenable intended to be lazy
hr = new HttpRequest(....);

// p1 is a genuine promise
p2 = p1.then(v => hr);

//.... much later ....

p3 = p2.then(onFul, onRej);

In current Promises/A+, hr is forced when p2 is constructed, which probably doesn't break the code but is wasteful -- it doesn't need to be forced yet. And if p2 never happens to be used, hr didn't need to be forced at all.

In our newfangled AP2 promises (which Promise/A+ will also be adopting), p2 merely "accepts" hr. hr.then doesn't get checked or invoked until/unless p2.then is invoked, which is indeed as late as possible.

Second:

Since today this code will still typically work, and will in the near future successfully become lazier, we should expect a lot of such code to exist by the time we roll out our new standard. This brings us back to our need to chose between "compat-duck", where thenable is simply (typeof obj.then === 'function'), and "narrow-duck", where some additional marking is required.

If we wish to go with narrow-duck, we need to agree on and promote that additional marking asap, before more such code accumulates, or we need to give up on narrow-duck. We are faced not just with the legacy of all the existing promise libraries that would need to add this additional marking to their promises. We are also faced with all the users of these promise libraries who hand-roll their own thenables as above, to be assimilated by these promise libraries.

IMO, I expect it is already too late for narrow-duck, and that we will find we are already stuck needing to standardize compat-duck, even though it will misinterpret some other existing non-thenable uses of "then".

# Mark S. Miller (12 years ago)

On Mon, Aug 26, 2013 at 8:54 AM, Mark S. Miller <erights at google.com> wrote:

In current Promises/A+, hr is forced when p2 is constructed,

Sorry for the imprecision. Not "when p2 is constructed", but rather, in some turn after p1 is fulfilled. The point remains: This is potentially much earlier than when p2.then is invoked.

# Tab Atkins Jr. (12 years ago)

On Mon, Aug 26, 2013 at 8:54 AM, Mark S. Miller <erights at google.com> wrote:

Since today this code will still typically work, and will in the near future successfully become lazier, we should expect a lot of such code to exist by the time we roll out our new standard. This brings us back to our need to chose between "compat-duck", where thenable is simply (typeof obj.then === 'function'), and "narrow-duck", where some additional marking is required.

If we wish to go with narrow-duck, we need to agree on and promote that additional marking asap, before more such code accumulates, or we need to give up on narrow-duck. We are faced not just with the legacy of all the existing promise libraries that would need to add this additional marking to their promises. We are also faced with all the users of these promise libraries who hand-roll their own thenables as above, to be assimilated by these promise libraries.

IMO, I expect it is already too late for narrow-duck, and that we will find we are already stuck needing to standardize compat-duck, even though it will misinterpret some other existing non-thenable uses of "then".

Yes. I hate it, because it's a terrible stomping all over the object namespace, but I think we do have to accept that "promise-like" means "thenable". ;_;

Note, though, that .flatMap() won't recognize a thenable as a promise. There's no "promise-like" where flatMap() is concerned, because flatMap() is the monadic operation, and should be shared by all monads, so you have to distinguish types nominally rather than structurally. (Or by using a side-channel structural check, like a brand, which blends the boundaries between the two.)

This means that returning a thenable from a flatMap() callback will cause a TypeError rejection of the output promise. What about .resolve()?

# Mark S. Miller (12 years ago)

+1. Agreed on all counts.

What about .resolve()?

pseudocode:

    resolve(obj) => isPromise(obj) ? adopt(obj) : accept(obj)

The isPromise above is nominal. If obj is a non-promise thenable, as in the example, it will be accepted, not adopted. The isThenable check happens only on the input side of .then, and so forcing only happens at that point as well.

# Tab Atkins Jr. (12 years ago)

Sounds good to me.

# Juan Ignacio Dopazo (12 years ago)

On Mon, Aug 26, 2013 at 12:54 PM, Mark S. Miller <erights at google.com> wrote:

Since today this code will still typically work, and will in the near future successfully become lazier, we should expect a lot of such code to exist by the time we roll out our new standard. This brings us back to our need to chose between "compat-duck", where thenable is simply (typeof obj.then === 'function'), and "narrow-duck", where some additional marking is required.

(...)

IMO, I expect it is already too late for narrow-duck, and that we will find we are already stuck needing to standardize compat-duck, even though it will misinterpret some other existing non-thenable uses of "then".

I don't believe it's unthinkable to ask the community to update their implementations to match the spec either way. I see two reasonable options:

  • Instead of full compat-duck, add another method to the spec and duck check both. It can be flatMap() or done() if there's interest in either
  • Use some sort of brand check that is easy to implement and hard to match by mistake