A Challenge Problem for Promise Designers

Ron Buckton rbuckton at chronicles.org
Sat Apr 27 09:21:43 PDT 2013


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 at w3.org, Mark Miller, Dean Tribble, es-discuss

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
_______________________________________________
es-discuss mailing list
es-discuss at mozilla.org
https://mail.mozilla.org/listinfo/es-discuss
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20130427/9cb8f8ec/attachment-0001.html>


More information about the es-discuss mailing list