Long live the callbacks
blog.trevnorris.com
blog.trevnorris.com
As others have said, the shallow nature of his argument is indicated by the complete avoidance of error handling. Callbacks spin out of control very quickly, not because you have to handle every single callback, but you have to handle an error at every single point as well. Mix in some more advanced control flows like `map`, and it's insane.
Please, please do not heed advice to "not use function closures". That's completely ridiculous and unrelated to callbacks. Every single one of his tests creates a new function for every single iteration. Of course that's terrible if you are iterating 2e4 times. Over here in the real world, my closures are executed oh, about 100 times a second (e.g. completely negligible perf hit). This is a seriously flawed benchmark.
I've seen that a common argument for solving callback hell is to move all the functions up into the same scope and name them. I have no idea how you handle that in real code. It's hard enough to keep track of it in normal callback-style, but forcing me to jump around and create tons of temporary names sounds insane.
Learn you some functional programming, and why it's good. Also learn you some error handling, promises, and why callbacks are terrible.
(note: remember folks, async code is async for a reason. It needs to go fetch data from something very slow, like a network connection or hard disk. It's so silly to benchmark closure creation for IO-bound apps.)
Take Node's case where you're accepting HTTP requests. Each request is going to require a new instance, and call into the connection listener. If the connection listener contains all the function declarations for all events attached to the client then you loose the performance you could have had by v8 using a previously optimized function. The benchmark was intentionally made to show worst case. I don't expect every application to magically run 80% faster just because they moved some function declarations around.
Umm... every time you create a function in JavaScript, you create a closure whether you like it or not. (For "root-level" functions, you can think of the closure as being the variables which are global, i.e. belong to the window object.)
What the author really means is, don't define a function 1e6 x 1e3 times instead of just defining it once.
But that has nothing to do with nesting callbacks. In most cases, nested callbacks are only ever called once, because callbacks are being "chained" to each other.
So the performance the author is talking about has nothing to do with closures or even callbacks, but simply the overhead of re-defining functions thousands of times, which is obviously going to have much more overhead.
The right lesson is, nested callbacks are bad for readability, but in most cases have nothing to do with bad performance. The bad performance in this article has nothing to do with callbacks, but is all about the overhead associated with function re-definition.
To the author's point, I think they're addressing a common vector of criticism with respect to JS callbacks... Usually when someone is writing their "callbacks are evil" proof, they give the typical "callback pyramid of doom" example to prove their point...the author is just showing that if you do it right and flatten your callback structure out with named functions, etc., it solves a lot of those problems.. I think the author was trying to make the point that callbacks themselves are not the problem, per se, but rather the way they are commonly (and wrongly) used.
Further, the performance argument is trivially discarded by observing that in real languages like Go or Erlang, the speed of real code is just as fast if not faster than Node. You only get slowdown in Node because you have to pay to implement abstractions at the wrong layer. In the general case, callbacks are slower due to the need to keep creating stack frames over and over, instead of leaping into already-existing ones in progress, and things like Go will always have an advantage there.
Theoretically it's the same asymptotic cost. If you have state that has to be saved from I/O operation to I/O operation then you have to store that somewhere. In Go you would store it in the goroutine stack; note that allocating a goroutine stack requires an allocation. In Node you would store it in the callback environment, also requiring an allocation. In theory an optimizing JavaScript engine could reuse the same captured variable environment across multiple closures if it's the same, resulting in the same asymptotic performance as Go.
I said stack frame, not capture environment. You don't "store it" in the goroutine stack, it is the goroutine stack. Something like Node is going to be constructing and destructing frames much more often.
It isn't the language I use, but C# really handled this whole situation best with async/await. There you can just keep coding along in the main narrative of execution that you are concerned about even if it means under the hood the code execution stops and other things are done until your await statement continues. The error flow can usually be handled with some sort of exception mechanism. This article and the traditional pyramid of death are both really just hacks to get around lack of a language change like async/await.
The problem is readability and flow. async makes code flow (and perhaps more importantly , data flow) harder to grok without knowing the code well. closures don't help with that, but renaming doesn't help either. Especially if your thing is going to be named things like WriteToDiskAfterOtherFileRead or something.
things like yield and whatnot are embraced because it allows us to build things with clearer patterns. Callbacks have their place as well, but when you think about how most people reason about IO (even in async fashion), a better expressive tool would be appreciated.
correctness is more important than readability, though good readability sometimes may correlate with correctness.
[1] http://domenic.me/2012/10/14/youre-missing-the-point-of-prom...
He thinks he is advocating but stuff he is saying is embarrassing and it will become one of those things everyone links to it in ridicule and says "here is what to expect from using this project". Kind of like Rhyan Dalh famous "Node.js has linear speedup over multiple cores for web servers."
The writing style (Oh wait, I know. It's because they're Doing it Wrong) doesn't help it either. That kind of style to be credible has to come from the likes of Linus Torvalds and has to be backed up by serious experience or good solid proof.
Doing this in inner loops is going to cause a noticeable performance degradation. (Doing it in expensive asynchronous operations, which is what callbacks are often used for, is not going to make a measurable difference.)
In the second example, he creates a single closure and places it on the point's prototype.
Unsurprisingly, creating one object is faster than creating a thousand of them. I'm not sure why the author thinks this has anything to do with closures.
(function(){})()
They're a great part of JS these mathemagics.
do end {}
do{}while(0);If you use the return value of an IIFE, it ends up being more like a block in an expression-oriented language like Scala or Julia.
In general, closures are like closures, but IIFEs just use a closure to do something that every other language does without one.
if (sieve[(x / 8) >>> 0] & (1 << (x % 8))) {
^
TypeError: Cannot read property '0' of undefinedhttp://blog.trevnorris.com/2013/08/callbacks-what-i-said-was...
"I don't know who you are. I don't know what you want. If you are looking for generators or promises, I can tell you I haven't coded any. But what I do have are a very particular set of callback skills; skills I have acquired over a very long javascript career. Skills that make me a nightmare for developers like you. If you performance analyse callbacks correctly, that'll be the end of it. I will not look for you, I will not pursue you. But if you don't, I will look for you, I will find you, and I will destroy your benchmarks."