natively negotiating sync vs. async...without callbacks

Getify Solutions getify at gmail.com
Fri Dec 10 06:52:13 PST 2010


>
> I think given the side effects wanted these are the list of features in
> this proposal:
>
> 1. code run in this "statement" does so at the first opportune moment (sync
> runs immediately)
> 2. code in this statement does not start execution until some condition is
> true
> 3. code that encounters and error in this statement is routed to a
> construct in this statement automatically.
>


There's some other really important aspects of my proposal:

4. Control of execution (that is, the actuall calling of a function) is
linear in the chain in the calling code, rather than being controlled by the
code inside a function. If some function in my chain is a third-party
function I don't control and don't fully trust, then I don't want to pass it
any references to my code's functions (as a callback) or any of my code's
variables/scope. I simply want that function to "signal" to my code that
it's complete. My code then is able to "wait" for that signal (if I chose to
set up the statement that way with @ usage) and then continue evaluation of
the rest of the statement.

For instance:

function foo() {
   var p = promise;
   setTimeout(function(){
      p.fulfill(20);
   }, 1000);
   p.defer();  // this could be p.willDefer = true or some other less
confusing syntax,
                 // for flagging the current function's internal promise as
being deferred.
}

Now, let's assume that function is a third party code that I don't control.
All I know that it's "async/promise aware". Now, let's also assume I have a
`bar` function in my code that I need to pass an object to and have it do
something, but I only want `bar` to run after `foo` completes.

In other words, I want to kind of conceptually observe the async behavior of
`foo` and "subscribe" to his "completion" event. But I don't know or trust
`foo` enough to pass him any of my functions, nor do I want to pass him
references to any of my scope's variables (which he could then mess with). I
just want to "listen" for him to signal that he's fully complete, and then
move on.

function bar(y) {
   console.log(y.some_value);
}

var obj = { some_value: 10 };

foo() @ bar(obj); // whenever `foo` completes, output: "10"



5. I don't want to conflate a function's parameters, or its return value,
with the negotiation of the asynchronous chaining, suspension, resume,
message passing, etc. This is intended to preserve existing code to the
greatest extent possible.

 For instance:

function foo(x) {
    var p = promise;
   setTimeout(function(){
      p.fulfill(x+20);
   }, 1000);
   p.defer();

   return x+10; // return an immediate value
}

Again, let's assume that function is a third party code that I don't
control. All I know that it's "async/promise aware", and that it has both an
immediate return value and a deferred result.

Let's then say that in some part of my code, I just want to get the
immediate value, and I don't care what that function's
eventual/deferred result is:

var x = foo(4);
console.log(x); // "14"
...

This code would operate exactly like normal JavaScript. Even though foo()
has a deferred part to it, the calling code is choosing not to observe this
behavior, and just continue as normal, effectively discarding any async
behavior that may or may not be inside of `foo`. This is identical to
existing code patterns where I can call a function, and it can create async
side effects that I may or may not be aware of or care about.

Later in the code, I may decide that I do actually want to care about the
`foo` function's async behavior, so I want to construct a statement that
explicitly observes `foo` and "waits" for that fulfilled value. Without
needing any kind of different/changed `foo` signature in any way, I can then
choose to do this:

function bar(y) {
   var _x = (promise.messages.length) ? promise.messages[0] : 1;
   console.log(_x * y);
}

bar(3); // immediately, "3"
var x;
(x = foo(4)) @ bar(3); // after 1 second: "42"
console.log(x); // but, immediately: "14"

I recognize the syntax I'm proposing is quite different from normal code
(but I'm hoping eventually that it's seen as different in a good way, not a
confusing or bad way). But the syntax is explicitly and intentionally
separating the concern of async promise/deferral and
fulfillment/continuation from the concern of function parameter passing or
function return values.

I think I should be able to choose in my calling code if I want to construct
an @ statement that observes the deferral or if I just want to call the
function and only observe its immediate effects.

This is conceptually the same as me saying that if there's some function I
want to call that requires a callback, but for whatever reason I don't care
about the eventual effects, I can just pass it a dummy no-op callback and
accomplish the ignoring (the not observing) of that async effect.




> Given these I propose a different syntax, since #2 is the main issue with
> why it is being considered similar to a continuation. I think that this
> construct should declare up front instead of inline when it defers. This
> syntax is still poor but should help to illustrate (using a binary ! since
> js doesnt have one).
>
> foo = function(cb) { setTimeout(cb,1000) }
> bar = function(cb) { ... cb(1) ... }
> end = function(cb,x) { log(x) } // will log 1
> foo @ bar @ end ! err
>

Where does `cb` come from or get defined? Is it an explicit thing the
calling code would create, or is it an implicit thing that the @ operator is
going to create on the fly and pass to all the operand function calls?

In this code example, none of your `foo`, `bar`, `end` or `err` functions
are being explicitly called with a (). Is this intentional, to suggest that
the @ operator will implicitly call them? I'm guessing so, because it also
appears that the @ operator is taking care of passing that `cb` to all of
them. Or perhaps I'm still misunderstanding.



> So how does this relate to making things easier?
>
> 1. Error routing callbacks. This would be a god send if you have ever used
> node.js .
>

No question, I agree that "error handling" (but not strictly in the sense of
trapping JS errors, rather a higher level definition of "error" which is any
state the function decides is unexpected/undesired) needs to be part of the
mechanism.


> 2. No confusing continuations (many people like them but I find them hard
> to track code wise).
>

The confusion is a big part of the reason I want to contain them only
localized to a single statement. I'm relying on the fact that a lot of
developers already do things within single statements where they "tolerate"
the idea that what they're expressing in the single statement won't fully
complete right away.

jQuery chaining is an example of this. With jQuery chains, I can express a
chain of behavior, but in some circumstances, even though lexically the
whole chain will execute, the full completion effects of the chain will not
be finished when the chain is done parsing.

Similarly, LABjs does the same thing. And people don't seem to have too much
trouble understanding the idea that
$LAB.script("1.js").wait().script("2.js") will process the whole chain
immediately, but it will create an asynchronous chain of events that will
complete at a later time.


> 3. Passing data between callbacks would not require a closure construction
> which is generally costly.
>

Agreed. I think the message passing is important, but I want to separate it
from the function call signature or return value, and have it be instead
accessible through the function call's internal "promise" state.


> 4. What is being defered on does not break functions since it is an
> argument automatically passed (non-ideal since it could cause issues with
> existing code). Like Kyle proposed a keyword maybe needed etc.
>

On the contrary, I think by conflating the function's parameter list with
the negotiation token for the async behavior, we've explicitly created a
system where function signatures will have to change to work with the new
system.

By contrast, I'd like to have it so that all (or most) function signatures
that currently exist don't have to be changed, only the internal behavior of
a function needs to be slightly adjusted to "teach" it about this new
"promise" interface.

Lastly, I'd say that function signatures which currently accept a callback
also don't *have* to change. In fact, there may be some value in allowing
both styles (existing callbacks, and my new proposed style) to co-exist in
the same function, giving the calling developer the freedom to choose.
Imagine an event binding function:

function onclick(obj, cb) {
   var p = promise;
   obj.addEventListener("click", function(){
      if (cb) cb();
      else p.fulfill();
   }, true);
   p.defer();
}

function clicked() {
   console.log("btn clicked!");
}

var obj = document.getElementById("btn");
onclick(obj, clicked);
// or
onclick(obj) @ clicked();



--Kyle
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20101210/85364226/attachment.html>


More information about the es-discuss mailing list