This is a really great idea, Kris! A few comments inline...
On Wed, Oct 19, 2011 at 1:11 PM, Kris Zyp <[email protected]> wrote: > The topic of single-frame continuations has been discussed here before, > with the current state of ES.next moving towards generators that are based > on and similar to Mozilla's JS1.7 implementation. Generators are a powerful > addition to the language and welcome (at least by me). However, I believe > that this still leaves a distinct gap in functionality that forces the > majority use case for single-frame continuations to be handled by libraries. > A language feature that OOTB must rely on libraries to fulfill the most > wanted use cases seems like than ideal. > > I believe one could separate these single-frame continuations (or > coroutines, looking at it from the perspective of the behavior of the > function) into two categories. There are bottom-up controlled continuations, > where the caller of the coroutine function controls when the function will > resume execution. I think is equivalent to a generator. Generator functions > return an object with an interface for resuming the execution (and providing > values for the continuation) of the coroutine function. > > There are are also top-down controlled continuations. Here coroutine > functions can suspend execution when given an object (typically from one of > the functions it calls) that provides the interface to resume execution. > Resuming execution therefore is controlled by values returned from callees > instead of from the caller. It is worth noting that bottom-up controllers > can be turned into a top-down controller and vice versa with the use of > libraries (one can go either way). > > I believe that the overwhelming need that is continually and constantly > expressed and felt in the JS community in terms of handling asynchronous > activity is fundamentally a cry for top-down controlled single-frame > continuations (obviously not always stated in such terms, but that is the > effective need/solution). In terms of an actual code example, essentially > what is desired is to be able to write functions like: > > element.onclick = function(){ > // suspend execution after doSomethingAsync() to wait for result > var result = <some operator> doSomethingAsync(); > // resume and do something else > alert(result); > }; > > Generators directly solve a problem that is much less significant in normal > JS coding. While it is exciting that generators coupled with libraries give > us a much better tool for asynchronous use cases (the above can be coded > with libraryFunction(function*(){...}), my concern is that the majority use > case is the one that requires libraries rather than the minority case, and > does not promote interoperability. > > Now why should we consider something now when previous alternatives to > generators have failed to gain traction? Previous proposals have avoided a > specifying a direct interface on top-down objects to leave the door open for > different possible interfaces for resuming executions, or different possible > "promise" styles. We have wisely deferred to libraries when different > possible approaches have yet to be explored within the JS community. A > couple years ago there were numerous approaches being explored. However to > truly follow through with this strategy we should then proceed with language > design when convergence does in fact take place. A couple years later, I > believe the landscape has dramatically changed, and we indeed do have > significant convergence on a promise API with the "thenable" interface. From > Dojo, to jQuery, to several server side libraries, and apparently even > Windows 8's JS APIs (from what I understand) all share an intersection of > APIs that include a then() method as a method to define a promise and > register a callback for when a promise is fulfilled (or fails). This is an > unusual level of convergence for a JS community that is so diverse. I > believe this gives evidence of well substantiated and tested interface that > can be used for top-controlled single-frame continuations that can easily be > specified, understood and used by developers. > > My proposal is to allow the use of the "yield" keyword in standard > functions (not just generator function*'s) with the following semantics: The > "yield" operator is prefix operator that takes a single operand (variant of > AssignmentExpression, just as within generator function*s). When a "yield" > operator is encountered in the execution of a standard function (not a > generator), the operand value is examined. If the value is an object with a > "then" property that is a function (the object is AKA "promise"), the > execution will suspend, preserving the context for when the execution is > resumed. The operand's "then" function will be called with a "resume" > function as the first argument, and a "fail" function as the second > argument. If and when the "resume" function is called, execution of the > suspended function will resume. The first argument of the call to the > "resume" function will be provided as the result of the evaluation yield > operator within the resumed execution. If the "fail" function is called, > execution will be resumed with the value of argument to the fail function > being immediately thrown from the yield expression. Once the "resume" > function or the "fail" function is called, any subsequent calls to either > function should result in an error. The mechanics of context preservation > should otherwise follow the same principles as generators. > > When the executing function is suspended, it will return a new object with > a "then" method. The object's then() method may be called and the first > argument can be a callback that will be called when the executed function > eventually is finished (encounters a return statement or end of the body > after resuming execution). The return value of the function will be provided > as the first argument to the callback. If the executed function eventually > throws an error, the second argument provided to the then() method will be > called. > > If the value is not an object with a "then" property that is a function, > the operand value is the immediate result of the evaluation of the "yield" > expression and execution is not suspended. > > Here is a simple example of usage: > "use strict" > function delay(ms){ > // a function that will yield for the given milliseconds > yield { > then: function(resume){ > setTimeout(resume, ms); > } > } > } > IIUC you're proposing language-level support for promises, right? There's no getting around it -- you're spec'ing an interface for the unary yield operator to interact with. So why not go all out and have the language stratify `then` for you with private names? I know we've done just fine with the namespace pollution but if the language were to grow first class promise support it really shouldn't have this wart, and it doesn't need it. There's no need to make the same tradeoffs as the CommonJS Promises specs. There's a third way with private names and a de jure namespace to hang a "promise" branding. It's less attractive, but your example could be written something like this: import then from @promise; // `then` as a private name function delay(ms){ var obj = {}; obj[then] = function(resume) { setTimeout(resume, ms); }; yield obj; } An aside: this also demonstrates the need for object literal syntax with private names -- what's the latest on this? I like this looks of this syntax (but it may diverge too far from standard `private` usage to be palatable): import then from @promise; function delay(ms){ yield { private then: function(resume) { setTimeout(resume, ms); } } } Maybe some other keyword? Anything but the @prefix :) > function fadeOut(element){ > // fade out by yielding for 20ms in each loop > for(var i = 0; i < 100; i++){ > element.style.opacity = i / 100; > yield delay(20); > } > } > > One of particular compelling aspects of this proposal is how elegantly it > works with existing code. Because jQuery (and others) follow the same > interface, we could immediately write the following with the existing jQuery > 1.5+ library: > > "use strict" > function showData(){ > // make a request, asynchronously wait for the response, put the result > in an element > element.innerHTML = yield $.ajax({url:"target"}); > } > I agree this is compelling, but it would only take a small patch to jQuery (and other libs) to support a private names variant. If the language were to spec promises I can't imagine it being thenables, but every lib that uses promises (thenables or otherwise) would most certainly harmonize (heh) with whatever the committee decides. This is another argument for why the committee should take some stand -- agreement on an API is enough for shims to get written to help future interop along. > Obviously one could choose a different keyword to use. I'd imagine "await" > is one alternative. The drawback of "yield" is that it does behave > differently than a yield in a generator. However, generators behave quite > differently anyway, and top-controlled "yield" shares a very important > commonality. Using one keyword means there is only a single operator that > can result in a suspension of execution within a function body, making > easier to spot such occurrences and look for possible points where certain > variants should not be anticipated. Of course it is also nice to avoid > proliferation of keywords and introducing new previously unreserved > keywords. > Any thoughts on how this should interplay with generators? One side-effect of overloading yield is that it becomes impossible to wait for a promise inside a generator -- is this a feature or a bug? > There are also have been suggestions about potentially have language > support for queuing different operations on to promises (gets, puts, and > posts). I don't think proposal precludes such possibilities (it only > precludes the possibility of opaque promises that have no interface, but the > majority of devs I have talked to are pretty opposed to that idea, so that > doesn't seem like likely possibility). > I assume that if a function that yields, when called with a yield prefix, will return a promise -- is this correct? What if there exists a yield in the function but the function returns without hitting a yield in the codepath? No promise then? All in all this is a really interesting idea. I'm personally content with just generators, but this would take a lot of the pain out of trying to build on top of generators. Thanks Kris.
_______________________________________________ es-discuss mailing list [email protected] https://mail.mozilla.org/listinfo/es-discuss

