I agree, that async.js lies on the lower abstraction level than promises. But if I'd be the one writing node.js low-level APIs, I'll pick callbacks. They aren't pretty, but continuation-passing style is still functional.
Why would I prefer callbacks for low-level APIs? Because they are so damn simple. Everything is explicit and visible. On the other hand there are lots of details hidden under promise abstraction. Promises/A+ specification tries to reveal how they should work though. Yet for user land application code I might prefer promises as it's much easier to compose them. And functions like then/node.liftAll make it easy to wrap modules using callback interface.
The type signature of fs.readFile is
The promised version could be
Luckily, it's easy to make type synonyms in Haskell:
Returning to the original post, it's unfair to compare async.map with map & list. By the way, list is called q.all or when.all in Q and when.js respectively.
More fair is to compare when.all with async.parallel. Slightly modifying the fs.stat example:
As you can see, not so different. Source code layout is quite similar. The amount of boilerplate is same also.
However, what happens, and when is different.
Memoization of the results
James works thru a problem of avoiding hitting the file-system twice. You could reduce the problematic callback code and promised based solution into two snippets:
They look very similar, but their behaviour is different, as you can see from the comments.
Should we work in IO or Promise monad?
If we rewrite problematic callback code in Haskell, we will see the solution!
The output is
We run the computation twice. We can rewrite the code, to have only one runContT:
The output is
could be rewritten to:
I hope that the examples gave you some insights about promises and raw callbacks. As you see, using bare callbacks isn't that hard, if you know what you are doing.
And as mentioned in the introduction section, I'd prefer using callbacks for libraries' low-level API. They add no overhead, and you have all control about the execution. Also users of your library could select their promise library freely (without adding casting overhead).
For the application code, like the stat examples, promises are better. You could avoid callback hell without promises. Just name your functions and avoiding nested callbacks. But promises give you nice (looking) abstraction, which is also easier to use, not forgetting nuances like always dispatching then callbacks asynchronously.