In this article, we take a look at the shortcomings of async and promise libraries in Node.js, and what you can do to improve the quality of JS in your app.
Join the DZone community and get the full member experience.Join For Free
Bugsnag monitors application stability, so you can make data-driven decisions on whether you should be building new features, or fixing bugs. Learn more.
Specifically, it’s a TAX you pay every time you want to separate concerns and give some extra layer of indirection, such as when you apply some basic DDD layering. DAOs, Domain Services, Infrastructure Services, and stuff like that.
You want to add some details-hiding interface, and you end up adding steps in the arrow of the callback.
Another bad side-effect of the Callback TAX is that you have to spread callbacks all over the application, just to be prepared when an implementation goes from sync to async. For instance, say you can have some DAO that is hiding an in-memory, sync I/O, or object cache. If you don’t prepare it for an async flow of execution, the migration to an on-disk persistence propagates through all the upstanding layers of indirection.
Async is a library that provides a means to “verticalize” arrow code:
The other solution, common in many technologies, are Promises, Futures, Promises/A+, etc.
Let’s look at Q promises, a very good implementation that permits chaining, with something like this:
It appears to be a better solution than async. Cleaner, simpler. But also tightly coupled. I have to change all the contracts of my classes, not only the implementations, to use that library of promises. Try to imagine the cost of changing my promises library to another one. From this point of view, promises are a worse solution than async.
But both libraries (async and promises) have an added, deeper, hidden cost that can be dramatic, because of the strong coupling: testability.
There are some more options, like an extensive use of nested closures, or the use of reactive programming extensions, but I think they are solutions to different problems; in fact, I think you should not be forced to use them because of the technology you are programming. And in addition, they could be impractical for everyday programming.
The same is true for a massive use of “Tell, Don’t Ask” as an architectural style. Indeed, I see it in the vast majority of the codebases I know, and it’s not so clear that it is always the best solution (though it may be in a world of pure theory).
All these solutions make big efforts to make the asynchronicity of Node.js code easier to understand, in order to “mitigate” the problem while at the same time being respectful to the inner nature of the Node.js reactor.
So we can say this is a mere problem of syntaxis, namely, syntactic noise. If we could solve the syntactical problem, the code in its whole would be perfectly portable between sync and async technology or implementations. In fact, in general, we can state that every sync code can be written in async style. The contrary is not true.
Syntaxis encapsulation would give me the chance of changing from a sync implementation to an async one, for instance, inside a DAO class, without changing anything about the client code. Not even the syntaxis.
Besides, in the specific case of Node.js, I could completely avoid a huge (syntactical) problem in testability, and the cost of a bigger codebase.
A Logical Framework for the Solution
Now, I got the bigger part of the work done: identifying a logical framework in which to achieve an optimal solution. From here it’s only a matter of finding the technology/library/framework nearest to the solution I propose.
Many technologies have a standard solution that goes in this direction. Think for example of C# async/await, Java/Scala continuations, and some Coffee Script solution (IcedCoffeeScript).
Obviously, it is non-blocking.
Published at DZone with permission of Christian Ciceri . See the original article here.
Opinions expressed by DZone contributors are their own.