Callback Heaven: Continuation Passing Style Patterns for JavaScript

Update: I published an npm module for callback based async/await.

There are many approaches to deal with deeply asynchronous code in JavaScript. One of the most popular option is to use promises. I don’t have anything against promises, they have benefits and provide a nice abstraction around exceptions. One of my best friend is a promise, but…

I was recently burned when a promise library I was not even using, that came embedded in a dependency, was silently swallowing all uncaught exceptions in a service I built. This caused the service to silently freeze when it should have been crashing, triggering an alert and recovering automatically.

Promises are a caching layer for results and errors along with a state machine to manage this cache enabling you to attach a continuation later. They introduce more stateful objects than necessary in programs and I’ve seen them be passed around in ways that make it hard to follow the flow of execution. This makes them a bit like goto statements. At least when used outside of async/await statements, they make it easy for execution to jump in ways that are difficult to follow.

If like me, you like to avoid too much syntax sugar and prefer more functional, encapsulated and stateless code, if you like your execution flow to be mostly top to bottom and if lisp-like amounts of brackets don’t scare you, a low sugar alternative is to use plain callbacks in a pattern called Continuation Passing Style. This promiseless style may help you avoid what I like to call code diabetes, where important state is hidden behind syntax sugar and dangerous corner cases lurk.

Continuation Passing Style basically means that where a function would normally return, there is instead a function call to a callback that contains the rest of the program execution. In JavaScript this callback is usually named “done” and is passed as the last parameter of functions.

CPS is only needed when writing functions that call asynchronous code such as web api calls, calls to a db etc. When there is nothing asynchronous inside a function, just use normal direct style.

It’s a fairly straightforward and widespread pattern but it’s useful to illustrate its flexibility with a few examples. JavaScript’s great support for anonymous functions and closures makes this simple and powerful.

Direct style:

function direct(x){ 
return x*x;
}

CPS (unnecessary because code is not asynchronous, example only):

function cps(x,done){
done(x*x); //call done instead of returning.
}

The cps function would be called like this:

cps(2,/*then*/function(res){
console.log(res);
});

Which is analogous to direct style

res = direct(2);
console.log(res);

You could also do:

cps(2,console.log);

Which is more analogous to:

console.log(direct(2));

Lets do a slightly more complex example, direct style:

function direct2(x){
if(x>10){
return Math.pow(x,2);
}else{
return -1;
}
}
direct2(11);//121
direct2(9);//-1

CPS:

function cps2(x,done){
if(x>10){
done(Math.pow(x,2));
}else{
done(-1);
}
}

cps2(11,/*then*/function(res){
console.log(res);//121
});
cps2(9,/*then*/function(res){
console.log(res);//-1
});

or, if you have an asynchronous version of pow

function cps2b(x,done){
if(x>10){
CPSMath.pow(x,2,/*then*/function(xx){
done(xx);
});
}else{
done(-1);
}
}

cps2b(11,function(res){
console.log(res);//121
});
cps2b(9,function(res){
console.log(res);//-1
});

CPSMath.pow would be something like:

CPSMath.pow=function(x,y,done){
done(Math.pow(x,y));
}

Let’s say you want to add additional processing to your normal function before returning:

function negate(x){return -x};

then in direct style:

function d3(x){
if(x>10){
y= Math.pow(x,2);
}else{
y= -1;
}
return negate(y);
}
d3(9);//1
d3(11);//-121

To do this fully in CPS:

CPSnegate=function(x,done){done(-x);}

you could do

function cps3(x,done){
if(x>10){
CPSMath.pow(x,2,/*then*/function(xx){
CPSnegate(xx,/*then*/function(processed){
done(processed);
});
});
}else{
CPSnegate(-1,/*then*/function(processed){
done(processed);
});
}
}
cps3(9,function(res){
console.log(res);//1
});
cps3(11,function(res){
console.log(res);//-121
});

A better option might be to use an inline function:

function cps3c(x,allDone){
var firstStep=function(y,firstStepDone){
if(y>10){
CPSMath.pow(y,2,/*then*/function(yy){
firstStepDone(yy);
});
}else{
firstStepDone(-1);
}
}
firstStep(x, /*then*/function(frstStepRes){
//after first step do second step:
CPSnegate(frstStepRes,/*then*/function(processed){
allDone(processed);
});
});
}
cps3c(11,function(res){
console.log(res);//-121
});

You can also inline the additional logic instead of calling a function. For example direct style:

function d4(x){
if(x>10){
y= Math.pow(x,2);
}else{
y= -1;
}
return -y;
}
d4(9);//1

becomes

function cps4(x,allDone){
var f1=function(firstStepDone){
if(x>10){
CPSMath.pow(x,2,/*then*/function(xx){
firstStepDone(xx);
});
}else{
firstStepDone(-1);
}
};
f1(function(y){
allDone(-y);
});
}
cps4(9,function(res){
console.log(res);//1
});

The inner function could also be anonymous:

function cps5(x,allDone){
(function(firstStepDone){
if(x>10){
CPSMath.pow(x,2,/*then*/function(xx){
firstStepDone(xx);
});
}else{
firstStepDone(-1);
}
})(/*then*/function(y){
allDone(-y);
});
}
cps5(11,function(res){
console.log(res);//-121
});

CPS looping can be used when you want to loop with each iterations performed one after the other instead of the concurrency you get by calling asynchronous functions in a “for” or “while” loop.

Direct:

var anArray=[1,2,3];
var i=0;
while(i<anArray.length){
console.log(anArray[i]);
i++;
}
//rest of program

becomes

var loop=function(i,done){
if(i<anArray.length){
console.log(anArray[i]);
loop(i+1,done);
}else{
done();
}
};
loop(0,/*then*/function(){
//rest of program goes here. Only runs after all iterations of the loop are done.
});
//code here may run concurrently with loop if there are asynchronous calls inside.

Note that “loop” is not a reserved keyword in JavaScript. If the loop is the last step in another function that accepts a done callback you can simplify to something like:

function parentFn(done){
var loop=function(i){
if(i<anArray.length){
console.log(anArray[i]);
loop(i+1);
}else{
done();
}
};
loop(0);
}

and maybe call the loop anonymously:

function parentFn(done){
(function loop(i){
if(i<anArray.length){
console.log(anArray[i]);
loop(i+1);
}else{
done();
}
})(0);
}

There are a few other tricks that can help make callback based code more readable.

One is to put smaller conditional blocks of code at the top of functions.

getSomethingFromDb(sql, /*then*/function(err,results)
if(err){done(err);return;}//handle error at the top
getSomethingFromDb(sql2, /*then*/function(err, results){
if(err){done(err);return;}//handle error at the top
getSomethingFromDb(sql3, /*then*/function(err, results){
if(err){done(err);return;}//handle error at the top
//do more
});
});
});

This allows you to end series of nested callbacks with a nice lisp-y string of closing brackets that is not interspersed with small else branches related to far away code.

                });        
});
}); //callback heaven
});
});

Avoid doing this:

getSomethingFromDb(sql, /*then*/function(err,results)
if(!err){//larger and deeper block first.
getSomethingFromDb(sql2, /*then*/function(err, results){
if(err){done(err);return;}
getSomethingFromDb(sql3, /*then*/function(err, results){
if(err){done(err);return;}
//do more
});
});
}else{ //poo
done(err);
}
});

The trick is to order smaller and shallower blocks of code in if…else statements to be at the top to reduce the textual distance from parent blocks.

Note that this trick works with code as well as with longer human natural language sentences composed of multiple hierarchical words and phrases.

I could have written: “This trick works with longer human natural language sentences composed of multiple hierarchical words and phrases as well as with code” but “with code” would have ended up far and disconnected from its verb “works”. Sentences are easier to read when related words are close together.

In code, put any small block to handle an error first then write the rest of the function.

Another popular pattern that is illustrated in the above examples is to pass errors as the first parameter of done callbacks. This pattern is used in the Node.js core API.

Since calling done is used in place of returning, I also like to add a return statement just after the calling of done.

done(null,x);return;

This reduces the chances of accidentally calling done twice. Some people do it in a single statement “return done(null,x);”. The slight downside to this syntax is that it is suggestive of trying to return a value despite this not being the intent.

Another good idea is to use arrow functions instead of traditional anonymous functions for a lighter more succinct syntax with a better defined ‘this’ pointer.

function cps6(x,done){
done(null,x*x);return;
}
cps6(2,/*then*/(err,res)=>{
console.log(res);
});

For a more advanced example of the power of the CPS pattern, look at how it can be used to implement a probabilistic programming language by instead of always calling a fixed ‘rest of the program’ callback, calling one of a distribution of callbacks dependent on a random variable outcome.

It takes a bit of time to get used to CPS but once you get it, it’s easy and may save you from early onset statefulness outbreaks.

In case you are still not convinced, really the best case I found against using promises comes from, believe it or not, the pro promise chapter of a book on Javascript.

I don’t understand how the author managed to see the things described as positives.

The length of the chapter itself is a testament to how overly complex promises are. On top of bringing in a ton of jargon such as “thenable”, “rejection”, “fullfilment”, “future value”, “uninversion of control”, “revealing constructor”, it admits that promises do not solve most of the issues mentioned about callbacks in the previous chapter and often make things fail in more subtle ways.

It describes how you have to manually call things like Promise.race() to solve some of their problems hardly making things more automatic then callbacks.

The author talks about “Thenable Duck Typing” saying horrifying things such as

“Given that Promises are constructed by the new Promise(..) syntax, you might think that p instanceof Promise would be an acceptable check. But unfortunately, there are a number of reasons that’s not totally sufficient.

Mainly, you can receive a Promise value from another browser window (iframe, etc.), which would have its own Promise different from the one in the current window/frame, and that check would fail to identify the Promise instance.

Moreover, a library or framework may choose to vend its own Promises and not use the native ES6 Promise implementation to do so.”

and

“The standards decision to hijack the previously non reserved — and completely general-purpose sounding — ‘then’ property name means that no value (or any of its delegates), either past, present, or future, can have a then(..) function present, either on purpose or by accident, or that value will be confused for a thenable in Promises systems, which will probably create bugs that are really hard to track down.”

The author praises immutability but ignores the fact that promises add a state machine around every asynchronous operation. He mentions that promises can silently swallow errors and calls it the “Pit of Despair”.

There is much more in that chapter.

I find that this quiz also illustrates how Promises can easily trip people.

Does the async/await syntax do a good enough job of fixing the issues Promises bring? Maybe. It’s still too early to tell. I wish it was built on a more solid foundation. I looked at implementations and it seemed to me that building async/await on top of plain old callbacks would have been simpler, less stateful and more efficient. Update: Here is my attempt at an implementation (but without the syntax sugar of native keywords).

The whole “use promises everywhere” movement reminds me of the “use XML everywhere” movement that started at the end of the 20th century and lasted a couple of decades, over engineered solutions to problems that could be solved more easily with a bit of diligence, code structure and maybe teaching a few simple constructs.

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store