There are cases where gotos are good (most possible uses of gotos are not good). I needed to write JavaScript functions (for running in NodeJS) where I wanted to call the callback function just once in the end (to make things as clear as possible). In C that would be (this is a simplified example):
void withgoto(int x, void(*callback)(int) ) { int r; if ( (r = test1(x)) ) goto done; if ( (r = test2(x)) ) goto done; if ( (r = test3(x)) ) goto done; r = 0; done: (*callback)(r); }
I think that looks nice! I mean the way goto controls the flow, not the syntax for function pointers.
JavaScript: multiple callbacks
The most obvious way to me to write this in JavaScript was:
var with4callbacks = function(x, callback) { var r if ( r = test1(x) ) { callback(r) return } if ( r = test2(x) ) { callback(r) return } if ( r = test3(x) ) { callback(r) return } r = 0 callback(r) }
This works perfectly, of course. But it is not nice with callback in several places. It is annoying (bloated) to always write return after callback. And in other cases it can be a little unclear if callback is called zero times, or more than one time… which is basically catastrophic. What options are there?
JavaScript: abusing exceptions
My first idea was to abuse the throw/catch-construction:
var withexceptions = function(x, callback) { var r try { if ( r = test1(x) ) throw null if ( r = test2(x) ) throw null if ( r = test3(x) ) throw null r = 0 }Â catch(e) { } callback(r) }
This works just perfectly. In a more real world case you would probably put some code in the catch block. Is it good style? Maybe not.
JavaScript: an internal function
With an internal (is it called so?) function, a return does the job:
var withinternalfunc = function(x, callback) { var r var f f = function() { if ( r = test1(x) ) return if ( r = test2(x) ) return if ( r = test3(x) ) return r = 0 } f() callback(r) }
Well, this looks like JavaScript, but it is not super clear.
JavaScript: an external function
You can also do with an external function (risking that you need to pass plenty of parameters to it, but in my simple example that is not an issue):
var externalfunc = function(x) { var r if ( r = test1(x) ) return r if ( r = test2(x) ) return r if ( r = test3(x) ) return r return 0 } var withexternalfunc = function(x, callback) { callback(externalfunc(x)) }
Do you think the readability is improved compared to the goto code? I don’t think so.
JavaScript: Break out of Block
Finally (and I got help coming up with this one), it is possible to do:
var withbreakblock = function(x, callback) { var r var f myblock: { if ( r = test1(x) ) break myblock if ( r = test2(x) ) break myblock if ( r = test3(x) ) break myblock r = 0 } callback(r) }
Well, that is at close to the goto construction I come with JavaScript. Pretty nice!
JavaScript: Multiple if(done)
Using a done-variable and multiple if statements is also possible:
var with3ifs = function(x, callback) { var r var done = false if ( r = test1(x) ) done = true if ( !done ) { if ( r = test2(x) ) done = true } if ( !done ) { if ( r = test3(x) ) done = true } if ( !done ) { r = 0 } callback(r) }
Hardly pretty, I think. The longer the code gets (the more sequential ifs there is), the higher the penalty for the ifs will be.
Performance
Which one I choose may depend on performance, if the difference is big. They should all be fast, but:
- It is quite unclear what the cost of throwing (an exception) is
- The internal function, is it recompiled and what is the cost?
I measured performance as (millions of) calls to the function per second. The test functions are rather cheap, and x is an integer in this case.
I did three test runs:
- The fall through case (r=0) is relatively rare (~8%)
- The fall through case is very common (~92%)
- The fall through case is extremely common (>99.99%)
In real applications fallthrough rate may be the most common case, with no error input data found. The benchmark environment is:
- Mac Book Air Core i5@1.4GHz
- C Compiler: Apple LLVM version 6.1.0 (clang-602.0.49) (based on LLVM 3.6.0svn)
- C Flags: -O2
- Node version: v0.10.35 (installed from pkgsrc.org, x86_64 version)
Performance was rather consistent over several runs (for 1000 000 calls each):
Fallthrough Rate ~8% ~92 >99.99% --------------------------------------------------------- C: withgoto 66.7 76.9 83.3 Mops NodeJS: with4callbacks 14.9 14.7 16.4 Mops NodeJS: with exceptions 3.67 8.77 10.3 Mops NodeJS: withinternalfunc 8.33 8.54 9.09 Mops NodeJS: withexternalfunc 14.5 14.9 15.6 Mops NodeJS: withbreakblock 14.9 15.4 17.5 Mops NodeJS: with3ifs 15.2 15.6 16.9 Mops
The C code was row-by-row translated into the JavaScript code. The performance difference is between C/Clang and NodeJS, not thanks to the goto construction itself of course.
On Recursion
In JavaScript it is quite natural to do recursion when you deal with callbacks. So I decided to run the same benchmarks using recursion instead of a loop. Each recursion step involves three called ( function()->callback()->next()-> ). With this setup the maximum recursion depth was about 3×5300 (perhaps close to 16535?). That may sound much, but not enough to produce any benchmarks. Do I need to mention that C delivered 1000 000 recursive calls at exactly the same performance as the loop?
Conclusion
For real code 3.7 millions exceptions per second sounds pretty far fetched. Unless you are in a tight loop (which you probably are not, when you deal with callbacks), all solutions will perform well. However, the break out of a block is clearly the most elegant way and also the most efficient, second only to the real goto of course. I suspect the generally higher performance in the (very) high fallthrough case is because branch prediction gets more successful.
Any better ideas?
Not that I’m a Javascript expert, but is this what you’re trying to do?
function(x, callback) {
callback(first_truthy_test(x))
}
first_truthy_test(x) {
return test1(x) || test2(x) || test3(x) || 0
}
masse, it works perfectly in this case… but my “real” cases were much more complex. Bigger functions with branches, calculations and tests – and in different places I wanted to jump to the end to call the callback. So the example in the post was just about finding different patters for getting to the end of a function and executing a few lines there.
I realize (and feel grateful for the fact that) your example was oversimplified. But the pattern I show will work as long as you can refactor your real problem into testN() functions, no?
I take it you mean such a refactor is unfeasible. If you have the time, I’d be interested in seeing a (simple) example of why that is.
masse,
The real code looked a bit like:
I typically write the getXxx-functions them myself, so they can be what I want, like:
They could return an error and manipulate an output parameter (which would make your method possible):
(it would probably be one out-object with one property per handle)
Perhaps it would be possible to construct something like:
or perhaps better
…but I think none of those look very nice.
I did write another test program though, because there were things I was not completely happy with. I constructed it so your use of || worked perfectly. However, I found nothing worth reporting about my other test program, except perhaps that the ||-construction has about the same performance as the other good methods.
When programming javascript I think you should think “functional” instead of “procedural”. Javascript is not C, so writing it as one would write C is counterproductive đ
function method(x, callback){
var nzero = [test1, test2, test3].map(function(test){
return test(x);
}).filter(function(i){ return i != 0; });
if(nzero.length) {
callback(nzero.shift());
} else {
callback(0);
}
}
Martin! Thank you for your suggestion! I implemented and tested it. I did not have exactly the same code and environment available as in my original benchmark, so I will not update my article and my performance table with your functional “map”-version.
However:
1) It has the disadvantage that it runs all the tests even when one test fails. For error handling purposes this might not be a good idea. And for performance reasons it is not optimal.
2) Using map the way you suggested is ~40x slower than “withbreakblock”.
I have as I mentioned in my comment May 9 constructed a new, improved test scenario. I may some day write a new post and include this functional approach both in code and in the benchmark.