One of the most powerful parts of JavaScript is that it allows you to code synchronous (serially executing) code along side asynchronous code. But what is powerful also leads to more complication for a variety of common tasks. I’m going to explore and propose an addition to native JavaScript that I believe will help developers in negotiating async code tasks within a sync code base.

(note: If you already understand the problems with nesting callbacks for handling async code, and want to just get to the main point and skip over all the boring back-story, read my proposal)

What I’m going to be talking about is a complex and emerging topic with some specific descriptors attached, like “promises” and “defer”. It’s been pushed heavily recently by the server-side JavaScript community (CommonJS), but I think the usefulness of these concepts is just as valid in browser JavaScript. In fact, one major motivator for this exploration is the desire to design a pattern for sync/async coding that is useful in both contexts, so that more code can be written once and re-used.

However, I want to leave aside some of the formalities of the promise/defer conversation (mostly because I am by far not an expert on it) — I know just enough about them to be dangerous but probably not enough to be useful. I also want to try and separate opinions/preferences about syntax (although part of the goal is shorter/cleaner syntax) aside and talk mostly about the structural/functional needs. It’s impossible to talk only about the theory, so there will be a syntax I suggest, but I hope that the conversation that develops around my proposal isn’t side tracked by the “weeds” of syntax before we’ve thoroughly talked about the underlying behaviors.

There’s a rather long gist about my explorations of the idea if you’re interested in seeing the back-story to this post. I think especially interesting is the comment thread with some really good feedback from several people, including the venerable Brendan Eich himself (hint: he invented JavaScript).

A callback in your callback so you can callback

Almost universally, the common pattern in JavaScript for dealing with asynchronous coding is the idea of the callback. That is, taking advantage of the first-class status of functions, passing a reference of a function to an async mechanism, asking for the function to be “called back” when the asynchronous process finishes. But most anyone who’s ever written mid-complexity (or more) code with async functionality has run into several common problems with the callback pattern for async handling. At its most basic definition, what I’m trying to do here is come up with a better way to handle such tasks. Of course, it goes much deeper than that, but that’s probably the best mental place to start.

Let’s first take a look at some example code written in the traditional callback style to illustrate some of these issues:

function xhr(url, callback) {
   var x = new XMLHttpRequest();
   x.onreadystatechange = function(){
      if (x.readyState == 4) {
         callback(x.responseText);
      }
   };
   x.open("GET",url);
   x.send();
}

setTimeout(function(){
   xhr("/something.ajax?greeting", function(greeting) {
      xhr("/else.ajax?who&greeting="+greeting, function(who) {
         console.log(greeting+" "+who);
      });
   });
}, 1000);

OK, so in this code, we want to wait 1 second (why? who cares!) and then we want to fire off an Ajax request to get some content. Next, we use that content to make a second Ajax request. Finally, once we have both pieces of content fetched, now we print them out to the console.

Firstly, the syntax ugliness of nesting callbacks 3 levels deep illustrates the inefficiency of this pattern. We accept that this is status quo, but in reality, I think this is an example of how the language is not properly (natively) supporting the common programming patterns found in modern usage of the language. We’re having to hack around with ugly, complicated, and thus harder-to-maintain syntax because that’s basically our only direct option.

Now, let’s consider what happens if I want to take that functionality and make it into some sort of general, reusable piece of code. There’s a few different ways this can be done:

function doSomethingCool(url_1, url_2, callback, delay) {
   setTimeout(function(){
      xhr(url_1, function(greeting) {
         xhr(url_2+greeting, function(who){ callback(greeting, who); });
      });
   }, delay);
}

doSomethingCool("/something.ajax?greeting", 
   "/else.ajax?who&greeting=", 
   function(greeting, who) {
      console.log(greeting+" "+who);
   }, 
   1000
);

Look at the brittleness we’ve now had to introduce. First, because we’ve abstracted that a URL (the “/else.ajax” one) is something that should be passed in as a parameter, we’ve now separated the URL passed in with the concatenation of the content received from previous Ajax request. We’ve created brittleness between code hidden away inside that general function and the calling code. If either of those changes, both pieces of code will have to change. In our example, the code is right next to each other, and not too hard. But what if this code is a method in an object abstracted away in a third-party library. This brittleness is a pattern that will lead to hard-to-maintain code and eventually failure.

Next, notice that we had to change the signature of the callback we pass in, so that it accepts both the `greeting` and `who` parameters, because now our calling code is separate from the context scope (closure) that gave us access to `greeting` in the previous code snippet without it being passed explicitly. And, we have had to use yet another anonymous function inside of `doSomethingCool` to compose the parameters `greeting` and `who` into a single call to our callback. This code will work, but it’s brittle and it fails the “sniff” test — if it seems to be too complicated, it’s probably wrong. Is there anything better we can do?

function doSomethingSortaCool(url, callback, delay) {
   setTimeout(function(){
      xhr(url, callback);
   }, delay);
}

doSomethingSortaCool("/something.ajax?greeting", function(greeting) {
   xhr("/else.ajax?who&greeting="+greeting, function(who) {
      console.log(greeting+" "+who);
   });
});

Meh. This is not getting much better. We removed one layer of the nesting abstraction. But now our calling code is having to take care of the complication instead of our utility. `doSomethingSortaCool()` is a little more general now, but we’ve just shifted the problem and complexity to the usage code. It seems our nesting is just going to be difficult to deal with.

So, I’ve been talking thus far about difficulties not only with syntax but also with interaction among components (aka, steps in our asynchronous logic). But let’s also point out another issue that such a pattern has. This is an issue that is clearly more a concern for server-side JavaScript folks, but it’s inherent to core JavaScript and thus is something I’d like to see addressed, regardless of how effective it is in the browser context.

The concern is this: what if `doSomethingCool()` is a function we did not write and do not control? What if we are linking to a third-party library that we can’t fully “trust” to always behave as it should? The misbehavior could be intentional/malicious, or it could simply be poorly implemented. But in either case, by calling it, and specifically passing to it a reference to one of our functions, we are trusting that function to execute our function for us at the appropriate time. Misbehavior could be calling our callback too early, or too late, or more than once, or never, or passing not enough parameters, or passing the wrong data, etc.

Not only are we “trusting” that function to behave properly, but we also have basically very little way to directly assert and enforce that proper behavior. We can go to great lengths in our callback’s scope to keep track of when and how it is called, making sure it’s only ever called once, and that it’s passed proper data. But that’s a crazy amount of work to do for every single callback function we ever define. Clearly, this code is begging for a more robust system to negotiate these problems for us.

Let’s step back a little more general now:

X(..., Y);

`X` is possibly an asynchronous function, meaning it may not finish right away (but it might!). But we don’t want `Y` to finish until after `X` is done. Moreover, we want `Y` to be notified of the eventual result of `X` when it is complete.

We pass `Y` to `X` and expect that `X` will execute it for us. Not only that, but we expect that X will make sure to pass the right parameters to `Y`. What if we actually need to specify some of the parameters for `Y`? We can pass all those parameters to `X` and ask it to add them to the call correctly. Or we can “curry” `Y` into a partially applied function, and pass that intermediate function to `X` instead. Currying is a standard pattern, but it’s a little advanced, and it involves some complexities that may be prohibitive. It also uses implicit anonymous function wrapper(s), which hampers our code’s efficiency the more layers of wrapping we have to involve.

X(..., function(){ Y(..., Z); });

Here, we’re doing the same thing, but now we’re passing in a hard-coded function that will call `Y` for us. However, we’re passing that function to `X`, so we’re still trusting `X` to behave properly. Moreover, we’ve created brittleness because this callback must call `Y`, which must pass in `Z`. We saw above the complexities if we want to generalize those connections. Lastly, we’re also now trusting that `Y` will properly behave with respect to `Z`. If `X` and `Y` are third-party, and only our `Z` we control, we’ve doubled the ways that our code may assume proper behavior and fail because we’re not certain that it will indeed do what we want.

One final wrinkle: we often need to define both what happens if the asynchronous code `X` finishes successfully, and what happens if it fails in some way. So, with the callback paradigm, now we’re passing twice as many functions to every layer of abstraction, and again, we’re relying even more so on the potentially untrusted code to make sure one or the other (but not both!) of our callbacks are called under the appropriate circumstances.

The problem is that we can’t “trust” (in the sense we’ve been discussing) code we don’t control, so we (that is, our direct code) need a way to be in control all of the execution, not the third-party code. Also, messaging between the async steps can get really complex quickly when nesting is involved. Lastly, parameter passing to subsequent steps must either be hard-coded or we must jump through lots of hoops to get them passed properly.

Sync-looking Async

X(...);
Y(...);
Z(...);

It would be ideal if our code could be written like this, and `X`, `Y`, and `Z` could all be asynchronous and the order would be preserved. As it stands, this is not possible. If any of those functions defers its completion by calling an async call, processing will continue, and thus order of completion will not be maintained.

“Continuations” are a programming concept where the code execution can suspend itself after `X(…)` is called, and the rest of the program can be “continued” later when `X` finishes, etc. One such syntax might look like:

X(...); yield;
Y(...); yield;
Z(...); yield;

I’m not suggesting this is how we should deal with our issue. But it at least is informative to look at the other ways these problems have been tackled in the past.

Put simply, we want/need a linear sequence of function calls. We need those functions to execute in order, even if one or all of them are asynchronous in nature. We need those functions to be able to cascade/propagate their results down the sequence, so that step 2 knows what the result of step 1 was (in case it needs it), etc. (btw, the “message passing” should not rely on shared state side-effects.) And we need a way to express what happens if any step in our sequence of function calls fails to fulfill its success path.

APIs

I hope I’ve now illustrated the issues that first-order function reference callbacks fall short of adequately addressing those needs. I’m not by any means the first person to articulate such issues or try to find solutions to these shortcomings. There are a number of proposals in the CommonJS community for handling “promises”. Dojo has a “deferred” utility. And there are many others.

I’ve even experimented with a chainable API syntax shim for handling them. For instance:

function delay(time) {
   var p = new Promise();
   setTimeout(function(){ p.fulfill(); }, time);
   return p.deferred;
}
function xhr(url) {
   var p = new Promise(), 
         x = new XMLHttpRequest()
   ;
   x.onreadystatechange = function(){
      if (x.readyState == 4) {
         p.fulfill(x.responseText);
      }
   };
   x.open("GET",url);
   x.send();
   return p.deferred;
}

delay(1000)
.then(function(){
   return xhr("/something.ajax?greeting");
})
.then(function(P){
   return xhr("/else.ajax?who&greeting="+P.value)
   .then(function(P2){ return [P.value, P2.value]; });
})
.then(function(P){
   console.log(P.value[0]+" "+P.value[1]);
});

So, notice how this (really naive and simple) experiment into promises that I did is trying to address some of the concerns. First and foremost, the `Promise` system (sort of a neutral trustable party) acts as a “proxy” of sorts between the various steps of this chain. I pass functions to the `Promise` mechanism by calling the `.then(…)` chained method of the object (aka “deferred”) that each of my functions returns. I’m not passing my functions to some untrusted third-party system, I’m passing them to a trusted neutral party. The `Promise` system only has one way that a function can signal that it’s finished: `fulfill(…)`.

This is guaranteed (by the `Promise` mechanism) to be callable once and only once. And `fulfill(…)` can take a “value” (aka, “message”) that it will pass along to the next step in the chain. Lastly, this syntax (while a little clunky itself) does address at least some of the awkwardness of nesting callbacks, parameter hard-coding, etc.

The point of me bringing this up is not to suggest that it’s the right way to deal with these problems. Most people seem to favor an alternate (not really chainable) syntax often called `when` syntax. It might look like this:

when(X, Y, Z);

That means, execute `X`. If successful, do `Y`, otherwise do `Z`. While chainability of this particular syntax is not straightforward (and is likely achieved with… nesting when’s), one great thing it does is address the concern that X shouldn’t have to execute Y or Z (or even know about them, for that matter). This `when(…)` is similar to my `Promise` experiment — a neutral party mechanism to negotiate promise deferral.

A modest (native) proposal

While there are many proposals for handling promises, and some of them are even eventually hoping to be a candidate for inclusion in the language, I’d say they are all mostly API focused. I believe my new proposal (while it has some API to it) is more focused at a lower level than API, and I hope is easier to integrate natively into the language.

Revisiting the generic example from above:

X(...) ,
Y(...) ,
Z(...);

Notice, I’ve used a “,” between `X` and `Y` and between `Y` and `Z`. This should not be interpreted to mean I’m suggesting that syntax. I just want to illustrate the general concept that I want to be able to easily specify a “chain” of 3 expressions (in this case, async function calls), and I want for those 3 to evaluate in order, even if the processing needs to suspend because of async deferral. I could just as easily list the example like this, and mean basically the same thing:

????  X(...)  ????  Y(...)  ????  Z(...)  ????

Execution/Processing Model

Further, let me state, in contrast to the formal “yield/continuation” model discussed above, I don’t think that `X` deferring itself means that the entire rest of the program needs to be yielded. In this case, I’ve arranged these 3 function calls into a single statement. So, the “yield/continuation” behavior would be localized only to the single statement. The statement will always be executed part-by-part, from left-to-right.

If the statement at any point suspends itself (defers completion), program execution would then continue immediately at the next statement. When the “suspended” statement is ready to resume, it will wait for the next “turn” available (in the exact same way in JS as any other async code waiting to execute), and will then continue on with execution of the next part of the statement. A statement can be “suspended” and “resumed” back and forth as many times as necessary.

The “suspension” is always controlled by the function calls in the statement (ie, yielding, not pre-emptive interruption).

The statement doesn’t get any special behavior with respect to any references to surrounding scoped variables. Each part of the statement will be executed at immediate-point-in-time taking into account whatever the current scope state is. There’s no suggestion that any type of state insulation where parts of the statement are protected from effects from other parts of the statement or from the rest of the program. In this respect, the async completing code behaves in pretty much the same way as a callback function that is executed later — it knows about references to variables, but it will use their current state when it executes that part of the statement.

Each part of the statement that is a function call which suspends (aka “defers”) its own completion, when it resumes/finishes, it will then be able to signal to the next part of the statement a “message” as the final result of the function call. Each part of the statement that is a function call which immediately follows an async part will be able to receive the “message” from the previous part, separate from and in addition to any explicit parameters to the function call itself. This “message passing” mechanism must not rely on shared state variable side-effects, but must instead be intrinsic to the statement handling mechanism.

The statement of this nature has 2 or more parts to it, but no part actually has to be a function call, or even if a part is a function call, that function call doesn’t have to suspend itself but can instead finish completely synchronously. The parts of a statement that are not function calls can be any kind of expression, but not other statements (like control structures, return, var, etc). These non-function-call parts (general expressions) cannot suspend themselves, and must therefore always be evaluated/executed synchronously immediately. Only a function call (even an executing function expression) may suspend itself for the purposes of async.

???? X()  ????  (Y = 10)  ????  Z() ????
            // `Y = 10` would be an immediately fulfilled promise expression
            // that moved execution onto the next expression in the statement.

As is true for the rest of JavaScript (which does not yet have { } block level scope), the only way to contain multiple statements inside a single part of the main statement is to wrap them in an inline function and execute that function. There are no special semantics or rules for each individual part of the main statement, except that each of them must be a valid expression.

Each time a function is called that may suspend itself, there must be a way to specify in the statement both a success path (if the function finishes as expected) and a failure path (if the function completes but not with expected results).

Syntax

Let me get specific about my idea for syntax based on the context described above. But I won’t spend too much time on it, because the syntax is less important than the execution/processing model.

I propose that the parts of the statement in question be separated by a new infix operator: @. The @ operator is like the , operator in that it evaluates multiple expressions one at a time, but it has the special behavior that it understands when a function suspends/defers itself, and suspends evaluation of the rest of the statement until that part completes.

As stated above, each operand of the @ operator can be a function call, but may just be a general expression. If an operand is a function call, and it suspends/defers its completion, the @ operator will wait to continue. Otherwise, immediate resolution will proceed through the statement, expression by expression, left to right.

The @ operator also has a syntax similar to the ?: ternary operator which expresses the success/failure conditional paths. Only one of the two expression clauses will be executed, depending on how the function chooses to resolve itself (fulfillment or failure).

Lastly, the message passing described above is implicitly handled by the @ operator mechanism, exposed only to function calls in the statement.

Let’s revisit an earlier example and see how the @ operator would be used:

function delay(time) {
   var p = promise;  // `promise` is now a special auto variable
                     // like `arguments` or `this`
   setTimeout(function(){ p.fulfill(); }, time);
   p.defer(); // used to flag this function as deferring/suspending 
              // its completion to be async
}
function xhr(url) {
   var p = promise,
         args = p.messages || [],
         x = new XMLHttpRequest()
   ;
   x.onreadystatechange = function(){
      if (x.readyState == 4) {
         args.push(x.responseText);
         p.fulfill.apply(null,args);
      }
   };
   if (p.messages) url += p.messages[0];
   x.open("GET",url);
   x.send();

   p.defer();
}

delay(1000) @
xhr("/something.ajax?greeting") @
xhr("/else.ajax?who&greeting=") @
(function() {
   console.log(promise.messages[0]+" "+promise.messages[1]);
})();

As you can see, I chain 4 function calls together with @ in between, the first 3 of which are async and deferred. As discussed in the processing model above, and also in the required/desired behaviors for an async/promise/defer system, this syntax accomplishes all those basic goals.

The simple “API” portion of my proposal is how you interact with the “promise” mechanism from inside of any function. There is a native auto variable `promise` (similar to `arguments` or `this`) which represents that function call’s promise. The `promise` object has a `fulfill(…)` method and a `fail(…)` method, for (not surprisingly) fulfilling the promise or failing it. Lastly, you opt into deferring a function’s completion by calling `defer()` on its promise object.

Both the API and the operator are my basic first pass proposal, and are open to input/adjustment.

Summary

I’m not trying to solve all of the issues that a language like JavaScript has with dealing with complex async/chained logic through promises/defers. I’m not suggesting that noone will ever have to use a promise/defer “lib” of some sort for the more complex negotiation tasks. I’m not suggesting that @ will be the cure all.

What I am suggesting is that the best first step to take in helping deal with sync/async negotiation in JavaScript is for there to be a small, narrowly defined, natively implemented mechanism (in my idea, as a special operator), which will serve as a building block for more complex usage, and will simultaneously provide very streamlined and simple syntax for a wide array of common sync/async tasks (like event binding, as seen in the original gist exploration).

I genuinely want to avoid some of the syntactic disagreements that have held back promise/defer API implementations by stripping down a significant part of it to a simple chainable infix operator. That doesn’t mean there’s no API, nor does it mean the function API I’ve proposed is correct. It just means that we need something efficient and native in JavaScript sooner, rather than later, to being dealing with sync vs. async issues. I think my proposal has some merit in the respect of being a productive step forward (and one of many necessary steps we must take as a language).

This entry was written by getify , posted on Tuesday December 07 2010at 02:12 pm , filed under JavaScript and tagged , , , , , , . Bookmark the permalink . Post a comment below or leave a trackback: Trackback URL.

9 Responses to “Native JavaScript: sync and async”

  • Kris Zyp says:

    I know that this isn’t a post about Dojo, but just for the record, Dojo does have a dojo.when() function. But in practice most Dojo developers tend to use .then() because it is more convenient.

  • Jorge says:

    If you don’t want/like to nest callbacks, can’t you just pass a context object around ?
    https://gist.github.com/735061

    Jorge.

  • getify says:

    @Kris- yeah, i prefer the chainability of the .then() style approach. but i know a lot of people in the commonjs community like .when() style instead. I see my proposal as a mixture between the two, honestly.

    @Jorge- nesting of callbacks is a problem, but using a common context object doesn’t help much — because you are still hard-wiring the calls. also, it misses a huge point above, which is that for “security” the desire is for the calling code to be in direct control of the execution, rather than handing a callback (or a context object that has functions on it) over to a potentially untrusted bit of code and asking it to execute for you.

  • Jorge says:

    @kyle: the callback functions are the equivalent of code blocks in synchronous execution, they have to be hard-wired (as they are in synchronous programs, aren’t they?), they are just the code labels of the code blocks you’re telling your program to jump to in an a-synchronous manner.

    The only problem that nesting callbacks solves is to have in scope previous context (in the closures created by nesting the callback functions). But you can solve it too by saving the context in an object just by making it visible/reachable from every callback. For example, passing it as a parameter to every callback.

    If you don’t want/like or can’t pass it around as a parameter, you can just create it in the outermost context. Either way you’re never going to need to nest any deeper than one level.

    WRT the “security” aspect of things, I don’t know, I have nothing to say.

    Jorge.

  • getify says:

    @Jorge-
    The reason I say “hard-wired” is that it’s not always that a function just needs to be called like context.foo();

    Sometimes, in my outer layer of calling code, I want to say: “execute foo, wait for it to finish, then execute bar, and when you execute bar, pass it these parameters.” It’s the parameter passing which often creates hard-wiring, or more awkward/complex code like currying, etc (as described in the above article).

    function foo(msg, callback) {
       setTimeout(function(){
          console.log(msg);
          callback();
       },1000);
    }
    function bar(msg) {
       console.log(msg);
    }
    
    function doFooBar() {
       foo("Hello ", function(){
          bar("World");
       });
    }
    
    doFooBar();
    

    Does that help explain what I mean by awkward “hard-coding”?

  • Jorge says:

    Consider the callbacks as the labels of code blocks, then you’ll see no need to pass so many parameters. Just as you do when you’re writing synchronous code (only that then, you don’t label the blocks).

    function doFooBar (a,b) {
    setTimeout(step1, 1e3);

    function step1 () {
    console.log(a);
    step2();
    }

    function step2 () {
    console.log(b);
    }
    }

    doFooBar(“Hello “, “world”);

  • getify says:

    @Jorge-
    You’ve only traded what I showed before for something even more complex and harder to maintain. You’re now relying on an outer closure to hold onto those vairables for step1() and step2() via the closure instead of explicitly passing them.

    What if step1() is not in my code, but it’s in a third party piece of code that I’m linking to? I can’t wrap that other code inside my closure. Nor can I change its signature to not need a parameter but just get it from its variable scope chain.

    Moreover, what if I don’t trust that third party code to have reference to my variables (because, who knows, they could intentionally or accidentally alter the values)?

    This is why I suggest that my calling code is where I need to be able to control the call to every function, including its parameters, its immediate return value, etc. If I hand that off to another function, even a special closure one I create like you’ve done, I’ve made things more complex and more brittle.

    foo(1) @ bar(2);

    That keeps the call to foo() and bar() in my control, and basically means that neither has to know about the other. This is essential for secure/trustable coding patterns.

  • getify says:

    By the way, if any of you are interested, here’s the current email thread on “es-discuss” list for this proposal/idea:

    http://www.mail-archive.com/es-discuss%40mozilla.org/msg05317.html

  • Toby Ho says:

    I’ve been following this thread with interest. But at this point I prefer using trampolining/generators over your proposal. Generators have been in SpiderMonkey since Firefox 2 and it allows you to write

    yield sleep(1000)
    var greeting = yield xhr('/something.ajax?greeting')
    var who = yield xhr('/else.ajax?who&greeting=' + greeting)
    console.log(greeting + ' ' + who)
    

    I have more code examples. See my full post on generators and trampolining.

Leave a Reply

Consider Registering or Logging in before commenting.

XHTML: You can use these tags: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>

Notify me of followup comments via e-mail. You can also subscribe without commenting.

 

Switch to our mobile site