Native JavaScript: sync and async

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); });

Page 2 of 6 | Previous page | Next page