
Callback Hell (2016) - Liriel
http://callbackhell.com/
======
lucideer
> In other languages like C, Ruby or Python there is the expectation that
> whatever happens on line 1 will finish before the code on line 2 starts
> running and so on down the file. As you will learn, JavaScript is different.

A lot of beginner guides to various programming languages make this mistake of
associating a certain property with a language as if it's inherent. In this
case, async code - while sold as a main feature of the NodeJS platform - is in
no way exclusive to, or even an inherent part of, Javascript/ES the language.

This may seem a nitpick, but I think it's an incredibly important distinction
for beginners (or at least, I think it's incredibly important not to mislead
beginners into believing in this limitation early on).

Another common example was, up until recently, that Javascript "wasn't
powerful enough" to do filesystem access, hardware operations, etc. A simple
side-effect of the environment the language was most commonly executing in
(the browser) was turned into an inherent limitation of the language in order
to "simplify things for beginners".

While bombarding beginners with a lot of info at the start is a bad idea,
these kind of misconceptions can be very damaging. They leads to a very narrow
idea of what's possible with (any) languages in general, and uninformed
decisions on what to learn as a result.

~~~
novaleaf
> In other languages like C, Ruby or Python there is the expectation that
> whatever happens on line 1 will finish before the code on line 2 starts
> running

actually, if you are into asynchronous programming (I am), I would claim the
opposite. When writing an async program in C# you need to be acutely aware
that in-between any two sequentially executed lines, entire threads of
execution could have been invoked and completed. If you don't design for that,
you WILL have race conditions.

Moving to nodejs 2 years ago, and something that I am still mindful of, is
that you will NEVER have race conditions, as the entire user code will execute
in a single thread, with interrupts only occurring between callbacks.

I like it because it's very easy to construct high performance professional
apps in NodeJs (most devs are not multithreading experts) but sometimes I miss
the idea of leveraging tons of cores. But that's why we scale horizontally
(more 1 or 2 proc servers, less 32proc servers).

~~~
antihero
You can still have race conditions anywhere IO is concerned, just not at a
thread level (because no threads).

~~~
simplify
This is correct. Race conditions are still the most common vulnerability in
web applications.

Take a classic example: a one-time use coupon. Without some kind of locking,
two simultaneous requests would be able to use the same coupon successfully.
Roughly:

    
    
      0.0s - [1] Client 1: Apply coupon
      0.0s - [2] Client 2: Apply coupon
      0.1s - [1] Server to database: Is coupon marked as used?
      0.1s - [2] Server to database: Is coupon marked as used?
      0.2s - [1] Database to server: Nope! All good.
      0.2s - [2] Database to server: Nope! All good.*
    

* Since the question was asked for both requests at the same time, the answer was "Not used" in both cases.
    
    
      0.3s - [1] Server to database: Update coupon as used.
      0.3s - [2] Server to database: Update coupon as used.*
    
             * Coupon has been used twice :(
    

Node's async model is not going to help you here. You will need something
more, such as a mutex.

~~~
Chyzwar
Nope, your ACID database will ensure that two writes in the same time for the
same record will not happen. You don't even need to ask database if coupon is
used. You just try to use coupon, if already used return message to user.

Node give you freedom from Thread Hell. You enjoy similar performance without
locks, threads, synchronisation. Race conditions happen but mostly with cache
invalidation where you can have limited consistency guarantees.

~~~
simplify
The problem isn't "two writes at the same time", it's the server thinking
everything's OK for two separate users.

If can model your database differently for this rudimentary example, then good
for you – you're thinking about race conditions. Many web programmers do not
think about them at all, and race conditions are usually not trivial to fix.

~~~
Can_Not
> Use ACID compliant Database or not

How is that specific to only NodeJs?

------
increment_i
As a curious and perhaps naive aside, I've been wondering why the programmer
should need to care about asynchronous execution of code at all. Can't it all
be abstracted under a procedural layer and let the OS worry about not blocking
anything? The advent of promises, async.js, and other paradigms tell me that
people still kind of want to write code that does one thing after another,
then another, then another.

~~~
dahart
> Can't it all be abstracted under a procedural layer and let the OS worry
> about not blocking anything?

No. There's no way around understanding that some of your code will run now,
and some will run later. It's imperative to understand this in places where
you mix sync and async code. It's not possible to avoid mixing them, after
all, the async functions have to be called by _something_.

You _could_ hide all async in a procedural layer if you accepted the
constraint that once an async call starts, none of your own code will run
until it returns. It wouldn't block the browser or OS, but it would block you.
That's the only way to abstract async away, but that's a constraint I think
most people would not be willing to accept.

> ...people still kind of want to write code that does one thing after another

I have no choice in the matter. I can't make use of the result of a REST call
until I actually have the result.

But, you're right at a fundamental level as well; people do want strict
ordering and simple to understand execution with predictable results. The
ideal is being able to read a piece of code and see & understand everything
about it based on the function you're looking at, and not a bunch of context
outside that function. It's the reason functional programming paradigms are
favored by so many.

~~~
hota_mazi
> > Can't it all be abstracted under a procedural layer and let the OS worry
> about not blocking anything?

> No.

Yes. That's exactly what coroutines achieve.

They transparently suspend and resume execution of async code in order to make
it look imperative.

See [https://github.com/Kotlin/kotlin-
coroutines/blob/master/kotl...](https://github.com/Kotlin/kotlin-
coroutines/blob/master/kotlin-coroutines-informal.md) for a good example.

~~~
dahart
No. Making it _look_ imperative and being abstracted away are two different
things.

Co-routines are not imperative, and you cannot treat them as though they are,
you still have to understand they're async to use them at all. That is not
"abstracted away", that is syntactic sugar.

Furthermore, this syntactic sugar only works locally when examining your
coroutines, or promises, or asyc/await code. You still have to trigger that
async code from somewhere, and the place it's triggered is _always_ mixing
sync and async code, so you can't sugar coat all of it.

There is no getting around knowing and thinking about the async factors when
writing async code. You cannot hide it with coroutines or anything else.

------
spcoder
I think this is a fantastic article in many ways. It clearly describes the
issues of callback hell and gives simple examples on how to clean it up.

Modern JavaScript has gotten very complicated lately because it's so flexible.
Many people are developing interesting frameworks to solve niche problems;
however, it feels like many of these solutions are overly complicated outside
the niche. Yet, developers are adopting these frameworks due to it being
trendy instead of choosing the correct tool for the job.

Many times, the correct "tool" for the job can be a mixture of an effective
standard library, small specific libraries, and good conventions like the
author describes.

I've been a programmer for 18 years now, and I am convinced that simple proven
solutions are the way to go.

~~~
mediocrejoker
Maybe it's because I'm a C programmer and not familiar with js but I felt this
article did a very poor job of describing "callback hell." I still don't have
a clear idea of what it is.

The author shows some code with lots of nested if/else clauses and claims this
is bad because it blocks; fair enough. Then they explain what callbacks are
and that some people have trouble understanding the asynchronous nature.

Then they immediately go into an explanation called 'How do I fix callback
hell?' and meanwhile I'm scrolling up to see if I've missed something.

Perhaps people unfamiliar with this particular problem are not the target of
this article, but that doesn't really make sense to me.

~~~
neogodless
I believe the primary issues are a mistaken belief that "every line of code
that gets executed should be read from top to bottom in the same order" (an
issue with any code that isn't modular) and the debugging issue of using
anonymous functions defined inline.

Readability of code is very important. Inline callback definitions hurt this.

~~~
danneu
Extracting inline callbacks leads to indirection, especially if you're only
extracting to avoid indentation. It doesn't make the code simpler or easier to
follow.

Promises and especially async/await let you read code top to bottom once more,
with other benefits like monad chains that catch synchronous failure.

~~~
neogodless
Any one section of code should have a "need to know" format. While you want to
know "what's happening" in order of execution, having all the "how it's
happening" code shouldn't be necessary.

For example, you want to know that the code is about to "getAccountData" but
you shouldn't need to know how. You see that it is called, and you see that
afterward, a callback called "loadAccountDataInReport" will be called. Why
should you see all the guts of those two functions inline?

~~~
danneu
Now let's say you're in a route handler where the buck stops, and you need to
compose any number of async calls, some branching on if-else logic and/or the
results of other async calls.

This is where the "just wrap it" argument breaks down.

Though I think promises are only slightly better when you have if-else
branches that add to the async chain. It's not til async/await where
Javascript finally hits its stride.

------
dpweb
The 'asynchonous problem' in JS is twofold. It makes it more difficult to
reason about the program, and is hell on readability, and readbility matters.
Support, maintenance, etc. The lack of an ability to structure async ops in a
simple way is the biggest drawback in the language.

Callbacks of course have the drawbacks mentioned. Sure, you can separate the
functions, smaller functions and that too is preferred, but still you get code
that can be difficult to reason about and read.

Promises, for all the hype, in my view clearly did not help matters much.
Promise code can be as weird looking or worse than callback code. Promises did
not solve the problem.

Async/await IS the solution. I've started converting callback and promise code
to it. It's a great new enhancement.

~~~
minitech
Promises _did_ solve the problem; async/await is just syntactic sugar for
them. The important thing is being able to start a task and pass around its
value on completion without worrying whether that completion has happened yet.
If you didn’t see an advantage to promises, you might have just been using
them like callbacks.

------
Jgrubb
I was looking through the vue.js documentation last night and noticed a
similar code style as the one this uses.

Are we back to "semi-colons are uncool"? It's so much eaier for me to glean
the intent when browsing JS written with semi-colons.

~~~
sratner
This looks kinda like [http://standardjs.com/](http://standardjs.com/), but
not quite.

------
shoover
These techniques seem to help code cleanliness in the small without attacking
the root issue. Names and error handling help, but modularization requires
more care than is demonstrated. The new module example separates the
boilerplate, yes, but it also hides document selectors that assume page
structure. So it's not actually modular. It's not reusable and it's brittle
for the one page it was written for (if you change the markup and don't
remember to change the module, the page breaks). It needs a way to pass in the
elements queried in callbacks.

------
matthewaveryusa
>Let's give em names!

The key piece of advice. It helps tremendously with backtraces if your program
crashes.

~~~
aljones
Also, generally good advice for everything and something to keep in mind when
designing systems.

Excel has one of the worst cultures when it comes to naming things. Easy to
blame the users, but tools end up being used the way their design encourages.
Excel gives you meaningless names. So most things have meaningless names.

------
wildpeaks
As much as I enjoy async/await, the filesize penalty once Babel-ified can
bloat up your bundles; better stick to Promises-only for now, even if it's
slightly more verbose.

However they can already be used in Node with the "\--harmony-async-await"
flag (and without flag in the upcoming Node 8).

------
coding123
Typescript and RXJS (with ts mappings) make CB hell much more enjoyable. There
is a learning curve to RX, but it's worth it.

------
pka
Cont, the mother of all monads [0] :)

[0] [http://blog.sigfpe.com/2008/12/mother-of-all-
monads.html?m=1](http://blog.sigfpe.com/2008/12/mother-of-all-monads.html?m=1)

~~~
marcosdumay
This is the one of the many things that are anti-features in most languages,
but become powerful best-practice tools when applied to Haskell.

------
anc84
I guess I should register abusingjavascriptfortriviallayoutinghell.com for
this? [http://i.imgur.com/JMzoOmv.png](http://i.imgur.com/JMzoOmv.png)

------
alanbernstein
Callback hell has confused me for a long time. I kept assuming there was a
good reason people write code that way, and there was something wrong with
naming my functions and passing them by name. The latter approach always made
more sense to me - it's more readable, and more intuitive.

If there is no real benefit to the nested anonymous function style, why do
people do it that way? It would never even have occurred to me to write code
like that if I didn't see it in javascript all over the place.

~~~
EvilTerran
I can think of one advantage - lexical closure: when your callback's body is
defined inside the calling scope, you can use variables from that scope inside
the callback; and the callback can assign to those variables & have the change
persist in the caller (only really relevant when the callback's invoked
synchronously, but can be very useful in those cases - eg, with
Array.prototype.forEach).

It _is_ possible to replicate these effects without closures, but it can get
pretty messy: the former by prepending the variables to the callback's
parameter list and .bind()ing them where you use it; the latter similarly, but
you also have to introduce a layer of indirection via an object to get a
"pass-by-reference"-like effect.

So sometimes it really is simpler to just use a function expression in-place.

To contrive a quick motivating example:

    
    
        var odds = 0, evens = 0;
        array.forEach(function (x) {
            if (x % 2) ++odds;
            else ++evens;
        });
        console.log("odd:", odds, "even:", evens);
    

versus

    
    
        function callback (counts, x) {
            if (x % 2) ++counts.odds;
            else ++counts.evens;
        }
        ...
        var counts = { odds: 0, evens: 0 };
        array.forEach(callback.bind(this, counts));
        console.log("odd:", counts.odds, "even:", counts.evens);
    

(And yes, I know you can just use "for...of" for this particular scenario in
modern JS; like I said, contrived example.)

------
chukye
I really don't see any problem with callbacks.

------
paradite
This problem has been addressed (in terms of proper coding style at least)
long time ago with async.js:

[http://caolan.github.io/async/](http://caolan.github.io/async/)

[https://github.com/caolan/async](https://github.com/caolan/async)

------
d--b
Callback hell is due to everything being async in javascript. Naming functions
and handling errors is how you may get by a little better.

For instance every database call is async, so for an operation that requires 3
chained calls, you'll have to nest these callbacks together.

------
weberc2
It's interesting that JS is still trying to figure out ways to make callback-
based asynchronicity less-painful while Go managed to skirt the issue
altogether. Go is readable, asynchronous _and_ parallelizable largely without
callbacks of any kind. Of course Go has its own problems, but I think they're
largely orthogonal to its async story (i.e., JS could adopt Go's async
strategy without adopting Go's more controversial features).

~~~
addicted
JS became a language it was never meant to be not because of its merits, but
because it is the language of the web.

Go is the language it was always meant to be, and a significant part of that
was handling async.

Once async/await comes along, JS will have a really nice solution, and to be
honest, I think it's pretty impressive that JS, with the history of its
development, has been able to acquire new technologies of this nature.

~~~
weberc2
Async/await will be an improvement to be sure, but I think it still leaves a
lot to be desired. In particular, I'm not a fan of bolting (a)synchronicity
onto the function definition; instead, it's nice to be able to run a function
in either a sync or async context. But yes, props to JS for continuing to
improve.

------
bluetwo
I agree with the structure the author is putting out there and have been using
it myself.

It was honestly a mystery whey people would use this hard to read nested
structure when you can write things in a more readable way.

The best theory I can come up with is that it is easier to write that way if,
for instance, you are answering a question on StackOverflow, and these
examples just find their way into production code via copy/paste without
people understanding how they work.

------
athenot
This is where syntactic sugar can be helpful. CoffeeScript can make the
example a lot more readable by removing the unnecessary braketing when the
last argument of a function is a callback (common case in node.js). The only
thing that's still ugly is the .bind() applied to a function, which somewhat
messes with the style. Other than that, this makes callbacks flow a lot more
naturally when reading source code.

(Of course, as it is for all stylistic considerations, it's a matter of taste,
not absolutes.)

    
    
        fs.readdir source, (err, files) ->
          if err
            console.log 'Error finding files: ' + err
          else
            files.forEach (filename, fileIndex) ->
              console.log filename
              gm(source + filename).size (err, values) ->
                if err
                  console.log 'Error identifying file size: ' + err
                else
                  console.log filename + ' : ' + values
                  aspect = values.width / values.height
                  widths
                    .forEach ((width, widthIndex) ->
                      height = Math.round(width / aspect)
                      console.log "resizing #{filename} to #{height}x#{height}"
                      this.resize width, height
                        .write "#{dest}w#{width}_#{filename}", (err) ->
                        if err then console.log 'Error writing file: ' + err
                    ).bind this

~~~
Mithaldu
Please don't perpetuate the else silliness of the (intentionally bad) first
example there. Stick a return in front of the log calls, lose the else, and
unindent all following.

~~~
athenot
Yes, that is of course preferable. There are much better error handling
strategies than what the example gave.

I was only commenting on the readability aspect, all other things being equal.

------
williamdclt
Lot of blabbling for a simple concept: do not abuse nesting. It's the case
with control statements, it's still the case with functions, and even more
with async functions.

Oh, and callbacks definitely exist in other languages, like C.

What's the deal with this trend of setting up a whole website for a (basic)
blog post?

~~~
meowpants
Don't be hatin'. It's a useful read for many.

"Simple concept" is subjective. I'd bet that discussing "simple concepts" with
an engineer or scientist would likely cause your mind to grasp and
instinctively try to relate to your own memorization of facts. But that
shouldn't preclude you from discussing what you do know, or for that matter,
sharing it with the world.

~~~
williamdclt
(answering you, but also for otherz saying the same thing below)

Yeah you're probably right, I've been a bit pedantic.

I feel this article is unnecessary long for the subject, it would be a good
length for something like "setting up a hadoop cluster", but having all sorts
of articles about all subjects is a very good thing. My bad.

------
EdSharkey
It's not "callback hell", it's _The Pyramid of Doooooooooooom_! Much more
dramatic and foreboding. ;-)

------
illuminati1911
My recommendation for handling callback hells, not just in JS but in any
language with callbacks is FRP.

[http://reactivex.io](http://reactivex.io)

Basically it's observer pattern on steroids. I'm not good at explaining things
though so check out the website.

------
dorianm
Same code than first example as synchronous ruby:
[https://gist.github.com/Dorian/91b965989c1f8f1c727b49d66477a...](https://gist.github.com/Dorian/91b965989c1f8f1c727b49d66477ade5)

(Add `begin; rescue; end` where errors need to be handled)

------
cookiesboxcar
Serious question: what is considered async?

From the site, I get that add(1+1) doesn't require a callback, whereas
downloadFile(url) does.

What about somethingHarderThanAddition(noExternalDependency)?

My question is, what is the threshold that I can assume that my code will run
sync, and when it cant?

~~~
SEMW
> what is the threshold that I can assume that my code will run sync, and when
> it cant?

Generally you won't have a choice. If something in your function calls another
async function, the function as a whole is forced to be async. If not, then it
won't be.

It's possible to make a function fake-async even if it doesn't call any async
functions, by just calling the callback at the end. But there's not much point
-- and callers may make assumptions about the the callback being called in a
separate iteration of the event loop, so doing that could cause subtle bugs.
That latter problem is solvable by calling the callback in a setImmediate
callback, but again, no point unless you have a good reason to.

(Exception: there are a few apis where you do have a choice, e.g node's
randomBytes can be either sync or async. Generally they're things that might
block, where the sync version returns an error if it can't satisfy
immediately. But generally you don't.)

------
thedjinn
After working with React Native for a while now I really began to appreciate
the usefulness of async/await. By expressing asynchronous operations in a more
linear fashion it becomes much easier to reason about them.

------
nojvek
Promises solve callback hell. Async await makes it even cleaner.

I think the way author is explaining is weird. It's very hard to follow logic
when functions are declared all over the place.

------
turdnagel
How about `async.waterfall`? It allows you to keep code relatively neat and
execute in visual order. It's the equivalent of a `Promise.then()` chain.

------
bjarneh
Not sure this problem goes away by giving names to anonymous functions, which
seems to be his suggestion

~~~
williamdclt
Nop, but it can be a bit better by using predefined functions rather than
inline ones.

~~~
bjarneh
Probably better, but the callback hell stems from a broken up control flow;
where we cannot read / understand what's going on with a linear (top to
bottom) scan of the source code (even for small sections of code).

I think programming where we have state and things can happen at any time,
where programs wait for events or something similar has proven to be difficult
to write/understand/maintain. The 30+ years it has taken to write the
(unfinished) Herd micro-kernel is a good example.

------
coldtea
> _Personally I use callbacks for 90% of the async code_

I stopped reading right there (well, it's near the end of the article anyway,
and I did continue, but you get my point).

------
zappo2938
I enjoy using promises.

~~~
j_r_f
Same. This seems to be an unpopular opinion on HN though.

~~~
jrochkind1
really? Weird. I love promises. What do people have against them?

------
intrasight
My brain couldn't parse due to lack of semi-colons.

------
agentgt
I don't consider myself a huge code style pusher but when I see two space
indentation I have to really ask... seriously... why???!!!

Its funny to me because the languages where you get massive nesting you almost
want greater indentation as it gets extremely confusing as to what scope you
are in.

While I prefer either 4 spaces or tabs (obviously not mixed) I'm not
completely revolted with 2 spaces in Algo languages like Java, C or Go
(although I suppose Go you have no choice) because you just don't have much
nesting.

But Scala and Javascript.... wholly crap is there a lot of nesting. Please for
god sakes if you absolutely need that much indenting either refactor or give
into the glorious evil that is TAB.

In all serious there is an accessibility thing with 2 spaces. You are
hindering people with bad eye sight. I just don't have the visual acuity
anymore to discern two spaces easily and I imagine there are many more. It is
sort of like building a building and making the stairs extremely steep because
you don't like taking lots of steps with your long legs.

~~~
d--b
It's because when you use 4 spaces, the lines just become too long. I use 4
spaces in C and 2 in javascript and that makes sense visually for me.

~~~
agentgt
Let me guess... It is hard for you to read long lines or your monitor isn't
big enough? You might call that an accessibility thing right? You get what I'm
getting at. You traded one accessibility that is fairly easy to fix (at worse
case you have line wrapping) for one that is almost impossible to fix (zooming
and large font don't really help).

~~~
d--b
It's hard for me to read long lines.

I don't think 4 spaces makes it easier to know in what scope I'm in when there
are a lot of scopes. The amount of white space is a bit overwhelming to me.

But you know, it's no big deal, just a matter of taste. Maybe 3 spaces is a
better answer, which I guess we'll never know...

~~~
agentgt
How long of a line are we talking here? Most coding standards are 80-120 with
maybe some at 140.

If you are say 4 levels deep the additional characters used over 2 spaces is 8
characters. You got whopping 8 additional characters to make an even longer
line of code (code to be interpreted or read) and screwed over the people who
need the indenting.

If long lines are a problem than just refactor. I agree long lines are hard to
read but there is a simple fix. Either refactor or just put in a line feed (or
however the PL deal with long lines aka \ for bash or python).

------
realharo
Last updated in 2012. A lot has changed since.

~~~
kraftman
Could you elaborate? Coming from Lua I always found the nesting of anonymous
functions messy and wondered why they weren't done as in the article.

~~~
Kiro
Promises and async/await. This page is so outdated it shouldn't exist.

~~~
mromanuk
He addressed more modern solutions at the bottom. I think, he is trying to
evangelize "you should write readable code, no matter of the limitations of
the language."

------
zump
As a C programmer working with a large codebase, I have come to HATE
callbacks.

Seriously, the worst feeling ever is tracing through a huge function tree,
only to run into function pointer dereference. Then you have to go on a wild
goose chase to find out when, where and what it will be assigned to.

STATIC TYPES PEOPLE.

~~~
pwdisswordfish
What does it have to do with static typing? Function pointers are perfectly
valid types. Also, can't you just use a debugger?

~~~
zump
Not really in a custom ASIC in an embedded environment.

~~~
digler999
then your problem isn't C callbacks, it's that your debugging tools are
inadequate.

------
realPubkey
A website? Really?

Why not just write an article on medium?

~~~
java_576
I prefer to own the URLs to my blog posts so that if I want to monetize my
site I have control over it.

------
mybrid
callback, n: 1. a function passed as a parameter. asynchronous programming, n:
1. code written to manage interrupts, often called signals.

This article is exemplary of why any educated and trained developer will not
go anywhere near the Javascript community.

~~~
dang
Please don't do the programming flamewar thing on HN.

------
crudbug
I feel this async computational burden has been passed on to the developers
without any abstraction, making the life of the foundational platform
developers easier.

IMO, platform IO libraries should support async behavior natively. Joe
Armstrong [0] explains this beautifully.

[0] [http://joearms.github.io/2013/04/02/Red-and-Green-
Callbacks....](http://joearms.github.io/2013/04/02/Red-and-Green-
Callbacks.html)

~~~
Normal_gaussian
> I don't know how you would write this in Javascript. I've written quite a
> lot of jQuery and know how to setup and remove callbacks. But what happens
> if an event is triggered in the time interval between removing an event
> handler and adding a new one. I have no idea, and life is too short to find
> out.

If you are manipulating inside a single function call it is atomic, JS is
single threaded and doesn't interrupt functions.

