async.js and promises

A friend of mine linked Callbacks are imperative, promises are functional: Node’s biggest missed opportunity blog post, during our own discussion about async.js vs promises.

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.

Promise type

The type signature of fs.readFile is

readFile :: String -> (Either Error Buffer -> IO ()) -> IO ()

The promised version could be

readFile :: String -> Promise Buffer

Luckily, it's easy to make type synonyms in Haskell:

newtype Promise a = Promise ((Either String String -> IO ()) -> IO ())

or using the Continuation Monad

type Promise a = ContT () IO (Either String a)

Returning to the original post, it's unfair to compare 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:

var fs_stat = node.lift(fs.stat); // node = require("when/node");

var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
var tmp =;
var statsPromises = when.all(tmp);

statsPromises.then(function(stats) {
  // use the stats

var fs_stat = _.curry(fs.stat, 2);

var paths = ['file1.txt', 'file2.txt', 'file3.txt'];
var tmp =;
var statsCallback = _.partial(async.parallel, tmp);

statsCallback(function (err, stats) {
  // use the stats



// helper to use with map

function oneparam(f) {
  return function (x) {
     return f(x);

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:

var callback = _.partial(fs.stat, "file1.txt"); // construct computation

callback(callbackify(console.log)); // run it and use the result

callback(callbackify(console.log)); // run it and use the result, again


var promise = node.lift(fs.stat)("file.txt"); // construct computation, and run it

promise.then(console.log); // use the result

promise.then(console.log); // use the result, again


function callbackify(fn) {
  return function (err, res) {
    if (!err) {

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!

import Control.Monad.Trans.Cont

type Promise a = ContT () IO (Either String a)

-- We use this just to be clear

data Stats = Stats String
  deriving (Show)

stat :: String -> Promise Stats
stat path = ContT $ statImpl path

statImpl :: String -> (Either String Stats -> IO ()) -> IO ()
statImpl path callback = do
  -- cause a side-effect

  putStrLn $ "stat " ++ show path
  -- execute the callback

  callback $ Right (Stats path)

promise :: Promise Stats
promise = stat "file.txt"

main :: IO ()
main = do
  runContT promise print -- kind of promise.done(console.log)

  runContT promise print -- but we are running the computation twice

The output is

stat "file.txt"
Right (Stats "file.txt")
stat "file.txt"
Right (Stats "file.txt")

We run the computation twice. We can rewrite the code, to have only one runContT:

promisePrint :: (Show a) => a -> Promise ()
promisePrint x = liftIO (Right `fmap` print x)

work :: Promise Stats -> Promise ()
work pstats = do
  s <- pstats
  promisePrint s
  promisePrint s
  return $ Right ()

main :: IO ()
main = runContT (work promise) print 

The output is

stat "file.txt"
Right (Stats "file.txt")
Right (Stats "file.txt")
Right ()

Similarly, the JavaScript example in the original post

var paths = ['file1.txt', 'file2.txt', 'file3.txt'];, fs.stat, function(error, results) {
  // use the results


fs.stat(paths[0], function(error, stat) {
  // use stat.size


could be rewritten to:

var paths = ['file1.txt', 'file2.txt', 'file3.txt'];

function work(error, results) {
  // use the results


  // use only the first result


// start the execution, fs.stat, work);


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.


comments powered by Disqus