Futures (was: Request for JSON-LD API review)

Ron Buckton rbuckton at chronicles.org
Wed Apr 24 10:10:05 PDT 2013



> -----Original Message-----
> From: annevankesteren at gmail.com [mailto:annevankesteren at gmail.com]
> On Behalf Of Anne van Kesteren
> Sent: Wednesday, April 24, 2013 7:08 AM
> To: Ron Buckton
> Cc: Tab Atkins Jr.; es-discuss
> Subject: Re: Futures (was: Request for JSON-LD API review)
> 
> On Wed, Apr 24, 2013 at 1:16 AM, Ron Buckton <rbuckton at chronicles.org>
> wrote:
> > * Future#then (tentatively adds the options argument in my proposal
> > for forcing synchronous execution of continuation)
> > * Future#done (tentatively adds the options argument in my proposal
> > for forcing synchronous execution of continuation)
> > * Future#catch (tentatively adds the options argument in my proposal
> > for forcing synchronous execution of continuation)
> 
> Why is that not up to the resolver? I think I'm missing something here.

In this case I am emulating a feature of .NET Tasks, similar to this: http://msdn.microsoft.com/en-us/library/system.threading.tasks.taskcontinuationoptions.aspx. This allows the consumer of the future to make the determination as to whether their continuation should execute synchronously or asynchronously. From the MSDN documentation:

"Specifies that the continuation task should be executed synchronously. With this option specified, the continuation will be run on the same thread that causes the antecedent task to transition into its final state. If the antecedent is already complete when the continuation is created, the continuation will run on the thread creating the continuation. Only very short-running continuations should be executed synchronously."

This is often used to reduce the overhead of scheduling the continuation task if the continuation is very lightweight. I say "tentatively" as I am not sure if it's worth keeping, but I wanted to experiment with it a bit. It does allow for a bit of "cheating" though, as you can schedule a then/done synchronously to get the value synchronously if its available, so I might drop it if it has an adverse impact. I have found that it is helpful in the "cancellation-by-future" scenario, as if all scheduling is asynchronous it's difficult to properly time cancellation. For example:

function someAsync(canceler) {
  return new Future(function(resolver) {
    /*b*/ 
    var handle = setImmediate(function() {
      /*e*/
      resolver.resolve();
    });
    /*c*/ 
    if (canceler) canceler.done(function() { 
        /*f*/ 
        clearImmediate(handle); 
    });
  });
}

/*a*/
var cancelSource = new Deferred();
var future = someAsync(cancelSource.future);

/*d*/ 
cancelSource.resolve();

The order of events is:
Turn 0 (T0):
a. execution starts in the current turn (T0)
b. the setImmediate will schedule the callback on the event loop to be executed in turn T1. 
c. the continuation is added to the canceler during T0
d. the canceller is resolved during T0, continuations will be scheduled in T2

Turn 1 (T1):
e. the future from someAsync is resolved

Turn 2 (T2):
f. the continuation for canceler is executed, which is too late to cancel.

There are at least two solutions to this: 
1. Provide the ability to schedule the continuation at (c) synchronously (similar to .NET Tasks and the proposed options argument).
2. Provide the ability to resolve the canceler at (d) synchronously (possibly by exposing the "synchronous" flag in the DOM Futures spec to the developer).

Please note that "cancellation-by-future" is an alternative to providing either Future#cancel or some kind of Future.cancellable(init) -> { future, cancel }. I would still much rather have a formal Future#cancel both as a way to explicitly cancel a future and its chained descendants (since I may not care about the value of the Future anymore) as well as a way to provide cooperative cancelation.

> 
> 
> > * Future.of (extension. coerces thenables)
> > * Future.resolve
> 
> What would be the difference between these?

Future.resolve either accepts the value or resolves a Future, but not a "thenable" (e.g. it must be a branded Future, including cross-realm). Future.of is similar to Future.resolve but will coerce a "thenable" (though it ideally looks for a callable "done" if also present, to prevent the possible and unnecessary overhead of allocating a new "thenable". 

> 
> 
> > * Future.some (with properly ordered reject array value)
> > * Future.every (with properly ordered resolve array value)
> 
> These are now fixed in the specification.
> 
> I added the simple static completed future constructors
> (accept/resolve/reject). I'm waiting a bit with the library until there's more
> implementations and some feedback.
> 

I'm not yet sold on having both accept and resolve on the resolver.  In the .NET world, a Task for a Task (e.g. Task<Task<T>>) is just that, and you have to unwrap the Task with something like a TaskCompletionSource<T>, which is explicitly like FutureResolver#accept. Libraries like Q.js automatically assume a Future of a Future is just a Future, implicitly unwrapping like FutureResolver#resolve. When I started digging into Promise/Future for JavaScript a few years back I was primarily invested in the first camp due to my experience with Futures in a type-safe language. Actively using Futures in a dynamic language has pushed me more towards simplifying a nested Future into a single Future due to its simplicity. I can see how providing both mechanisms could possibly satisfy developers in either camp, but could be confusing to API consumers if one library generally uses FutureResolver#accept and another library generally uses FutureResolver#resolve.

Is there a lot of interest to support both, or any previous discussion on the topic that I could peruse to understand the arguments for having either "accept", "resolve", or both?

Thanks,
Ron

> 
> --
> http://annevankesteren.nl/



More information about the es-discuss mailing list