W3C home > Mailing lists > Public > public-script-coord@w3.org > April to June 2013

Re: A Challenge Problem for Promise Designers

From: Ron Buckton <rbuckton@chronicles.org>
Date: Sat, 27 Apr 2013 16:21:43 +0000
To: David Bruant <bruant.d@gmail.com>, Tab Atkins Jr. <jackalmage@gmail.com>
CC: "Mark S. Miller" <erights@google.com>, "public-script-coord@w3.org" <public-script-coord@w3.org>, Mark Miller <erights@gmail.com>, Dean Tribble <tribble@e-dean.com>, es-discuss <es-discuss@mozilla.org>
Message-ID: <33D2646B20374248B7CAA8F25919AAFC42761C3E@BY2PRD0111MB494.prod.exchangelabs.com>
Here is a case where flattening helps:

function executeAndWaitForComplete(command) {
  return getJSON(commandUrl + command)
    .then(function (commandResult) {
      if (commandResult.complete) {
        return commandResult.model;
      }
      var statusUrl = commmandResult.statusUrl;
      return pollForCommandComplete(statusUrl);
    })
}

function pollForCommandComplete(statusUrl) {
  return new Future(function(resolver) {
    var poll = function (pollResult) {
      if (pollResult.done) {
        resolve.resolve(pollResult.model);
      }
      else {
        setTimeout(function() {
          getJSON(statusUrl).done(poll, resolver.reject);
        }, 500);
      }
    }
    getJSON(statusUrl).done(poll, resolver.reject);
  });
}

In this example, the server will receive a command from the client and will process it asynchronously. The client then needs to poll an endpoint to check for completion of the command. Just using Futures, flattening is helpful here. Without flattening, executeAndWaitForComplete would could return either a Future<object> OR return a Future<Future<object>>.  In a non flattening world, the developer would have to change the function to:

function executeAndWaitForComplete(command) {
  return new Future(function(resolver) {
    getJSON(commandUrl + command)
      .done(function (commandResult) {
        if (commandResult.complete) {
          resolver.resolve(commandResult.model);
        }
        var statusUrl = commmandResult.statusUrl;
        pollForCommandComplete(statusUrl).done(resolver.resolve, resolver.reject);
      }, resolver.reject)
    });
}

With flattening, the first version is less code for the developer. With flattening its possible to run into odd errors without some kind of static analysis since JS is not type safe.

Ron

Sent from Windows Mail

From: Tab Atkins Jr.
Sent: ıFridayı, ıAprilı ı26ı, ı2013 ı2ı:ı24ı ıPM
To: David Bruant
Cc: Mark S. Miller, public-script-coord@w3.org, Mark Miller, Dean Tribble, es-discuss

On Fri, Apr 26, 2013 at 1:39 PM, Tab Atkins Jr. <jackalmage@gmail.com> wrote:
> On Fri, Apr 26, 2013 at 6:36 AM, David Bruant <bruant.d@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
_______________________________________________
es-discuss mailing list
es-discuss@mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
Received on Saturday, 27 April 2013 16:22:33 UTC

This archive was generated by hypermail 2.3.1 : Tuesday, 6 January 2015 21:37:49 UTC