It's funny to me, I used to merely
tolerate JavaScript... I came from Ruby (and PHP/Java/C# before that)
where you could create these super complex control flow structures and
JavaScript felt so verbose and unwieldy and limited.
I mean, no inheritance? No method_missing? How do you write humane, readable DSLs with just functions and prototypes?
I've explored all kinds of tricks since then: OO libraries, promises, fibers, CoffeeScript classes and all its other sugar.
What's
interesting is I've swung fully back in the other direction. I find
myself increasingly drawn to very simple, very JavaScripty JavaScript.
Functions. Variables. Constructors. Prototypes. Closures. Arrays.
Objects. I think almost all of the new JavaScript features are more
trouble than they're worth.
Take promises for example. Here's some junky callbacky JavaScript:
Which certainly fixed the nesting problem. But you made your stack
almost incomprehensible, and you made your execution thread harder to
trace. And now the runtime is popping back and forth between your code
and the promise library every step. But the scariest thing of all is you
created a bunch of these promise objects that you could, like, return,
and some totally unrelated code could interact with it and totally fudge
up the control here. You took something that had really well bounded
semantics and turned it into something that is wide open to be messed
with in bizarre ways.
That's what power is. That's why people love
promises, you have power to do all manner of outlandish control flow.
Except I really don't want to have to debug your bizarre circus of
promises.
Don't get me wrong, I am certainly capable of debugging your circus of promises. I just really would prefer not to.
And anyway there's a way easier solution. Because JavaScript uses function scope, you can just do this:
libraryStuff(yourResponse)
function yourResponse() {
moreLibraryStuff(yourNextResponse)
}
function yourNextResponse() {
thirdLibraryStuff(finish)
}
function finish() {
console.log("done!")
}
It's easily traceable. You get a real call stack no matter where you
crash in that flow. And yeah, I doubled the number of symbols here, but
that just means I was forced to actually label my application code.
Which might not be such a bad idea anyway. In my production code all of
these functions are going to be several lines anyway, and it's quite
nice to be reminded that I should give them a good label.
Of course if
this was CoffeeScript, your stacktrace wouldn't have function names
because CoffeeScript threw away that feature in exchange for being able
to type "->" instead of "function".
I've noticed this pattern
over and over: someone gets frustrated because JavaScript doesn't give
you insane tools to quickly spin up bafflingly complex flow structures.
They find this frustrating, because they're used to baffling flow
structures from all of the crappy code they've been forced to get
comfortable with, so they write some insane library that lets you do
crazy stuff in JavaScript.
The same thing happened to me in Ruby. I
was so entranced by the power and magic of DSLs that I was constantly
looking for excuses to return chainable objects from functions and all
of this stuff. I would build things like that in production code, and
feel proud of myself that I made this super powerful thing with a
complicated implementation and a simple, prose-like interface.
But
almost every time, after living with the interface for a while, I would
realize that I could've solved the problem with just functions,
literals, and arrays, and structs if I had actually taken the time to
figure out the right abstractions.
Less and less do I think I need
some fancy new kind of function. More and more I think I need to be
more thoughtful about what the function actually does.