A Challenge Problem for Promise Designers

Tab Atkins Jr. jackalmage at gmail.com
Fri Apr 26 14:23:53 PDT 2013


On Fri, Apr 26, 2013 at 1:39 PM, Tab Atkins Jr. <jackalmage at gmail.com> wrote:
> On Fri, Apr 26, 2013 at 6:36 AM, David Bruant <bruant.d at gmail.com> wrote:
>> Le 26/04/2013 14:54, Kevin Smith a écrit :
>>>
>>> What exactly is the controversy here?
>>>
>>> I think we all agree with the semantics of "then" as specified in
>>> Promises/A+.  (If not, then we have a really big problem!)
>>>
>>> If so, then the only real controversy is whether or not the API allows one
>>> to create a promise whose eventual value is itself a promise.  Q does not:
>>> it provides only "resolve" and "reject".  DOM Futures do by way of
>>> "Future.accept".  As far as I know, there's nothing about Q's implementation
>>> that would make such a function impossible, it just does not provide one.
>>
>> I believe at this point the question isn't so much "can I build a promise
>> for a promise?", but rather "what should be the default Future semantics?"
>> Namely:
>>
>>     Future.accept(5)
>>         .then(function(x){
>>             return Future.accept(x);
>>         })
>>         .then(function(y){
>>             // is y a Future?
>>         })
>>
>> I'm arguing in favor of y being guaranteed to be a non-Future value. It is
>> my understanding others would want y to be a Future.
>> That would be the controversy as I understand it.
>
> No.  Future callbacks can return Futures, which then chain (the return
> value of then adopts the state of the callback's return value).  This
> is the big "monad" benefit that we keep talking about.

To lay it out even more clearly for any bystanders, in the following code:

getAFuture()
  .then(function(x) {
    return doSomeWork(x);
  })
  .then(function(y) {
    // is y a Future?
  });

The answer to the question is "no", *regardless of whether doSomeWork
returns a plain value or a Future for a plain value*.

If doSomeWork() returns a plain value, the future returned by the
first .then() call accepts with that value.  If doSomeWork() returns a
future that eventually accepts, the future returned by the first
.then() call waits until it accepts, and then also accepts with the
same value.

This all happens without recursive unwrapping.  It's just a feature of
how this kind of thing works (it's a result of Futures matching the
"monad" abstraction).

The only way that y will become a Future is if doSomeWork() explicitly
and purposefully returns a future for a future for a value (in other
words, Future<Future<x>>).  In this case, the future returned by the
first .then() call waits until the outer Future from the return value
finishes, then accepts with its value, and "this value" happens to be
a Future<x>.

This sort of thing does not happen accidentally.  It's hard to make
nested futures unless you're doing it on purpose, or you have no idea
what you're doing.  In the first case, we want to trust the author,
and in the second case, it's probably better for everyone involved if
the code fails in a fairly obvious way, rather than attempting to
paper over the problem.  If you're competent and just doing the
natural thing, the API naturally gives you plain values in callback
arguments and singly-wrapped futures in return values.

This is why I've been arguing against recursive unwrapping in the general case.

~TJ


More information about the es-discuss mailing list