2
\$\begingroup\$

I'm trying to get my head around asyncronous programming in node.js. My example is reading all the files from a directory.

I know I can avoid asyncronous programming by using the syncronous methods:

var fs = require('fs');

names = fs.readdirSync(".");
for(i in names) {
  fileinfo = fs.statSync( names[i] );
  if( fileinfo.isFile() ) console.log( names[i] + " has size " + fileinfo.size );
};

I know how to write it in the classic node style:

fs.readdir(".", function (err, names) {
  if (err) throw err;
  names.forEach(function(n){
    fs.stat( n, function(err, fileinfo) {
      if( fileinfo.isFile() ) console.log( n + " has size " + fileinfo.size );
    });
  });
});

But now I'm trying "functional reactive programming" with bacon.js and I'm not sure if I really get it. This is my first sketch:

var fs = require('fs')
  , Bacon = require("baconjs").Bacon;

fs.readdir(".", function (err,names) {
  if (err) throw err;
  Bacon.fromNodeCallback( fs.stat_with_filename, Bacon.fromArray(names) )
    .filter(".isFile")
    .onValue(function(stat) {
      console.log(stat.filename + " has size " + stat.size);
  });
});

But I had to monkeypatch fs to get the filename inside the stat object to achive this:

fs.stat_with_filename = function( filename, callback ) {
   fs.stat( filename, function (err,stats) {
     stats.filename = filename;
     callback( err, stats );
   });
};

Is there a better way to write this in node + bacon?

\$\endgroup\$
3
\$\begingroup\$

If you want to expose directory contents with stats as a Bacon EventStream or a Property, you can use the combine-family of functions to combine the filename and stats without monkey-patching:

var fs = require("fs")
var Bacon = require("../dist/Bacon.js")

// statsForFile :: String -> Property { filename :: String, stats :: Stats }
function statsForFile(filename) {
  return Bacon.combineTemplate({
    name: filename,
    stats: Bacon.fromNodeCallback( fs.stat, filename )
  })      
}

You can expose directory contents as a stream of filenames:

// directoryContents :: String -> EventStream String
function directoryContents(dir) {
  return Bacon.fromNodeCallback(fs.readdir, dir)
    .flatMap(function(names) { return Bacon.fromArray(names)})
}

Then you can compose these two like

function statsForDir(dir) {
  return directoryContents(dir).flatMap(statsForFile)
}

And see what happens:

statsForDir(".").log()

So that's how you can compose these things asynchronously using Bacon.js and FRP. This is not a prime example of where FRP is really useful yet, but when more composition is involved the benefits will be greater. Hope this helps!

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.