Skip to content

Instantly share code, notes, and snippets.

@AdrianRossouw
Created January 17, 2012 22:43
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AdrianRossouw/1629491 to your computer and use it in GitHub Desktop.
Save AdrianRossouw/1629491 to your computer and use it in GitHub Desktop.
// Underscore mixing for partial functions and async calls.
// Based on this microsoft post: http://msdn.microsoft.com/en-us/scriptjunkie/gg575560
_.mixin({
// Turn a normal function into an asynchronous version.
// It will return a function that expect's the last argument
// to be a callback expecting (err, result) as arguments.
wrapAsync: function(fn) {
return function() {
var args = _(arguments).toArray();
var next = args.pop();
var result = fn.apply(null, args);
next(null, result);
}
},
// Generate a closure with parts of the arguments already filled out.
// This does not bind 'this', so it is left up the caller to use the right
// context.
partial: function(fn){
var aps = Array.prototype.slice;
var argsOrig = aps.call(arguments, 1);
return function(){
var args = [],
argsPartial = aps.call(arguments),
i = 0;
// Iterate over all the originally-specified arguments. If that
// argument was the `_.__` placeholder, use the next just-
// passed-in argument, otherwise use the originally-specified
// argument.
for ( ; i < argsOrig.length; i++ ) {
args[i] = argsOrig[i] === _.__
? argsPartial.shift()
: argsOrig[i];
}
// If any just-passed-in arguments remain, add them to the end.
return fn.apply( this, args.concat( argsPartial ) );
};
},
// Wrap a non-async call with a partial.
partialAsync: function() {
var pArgs = _(arguments).toArray();
var fn = pArgs.shift();
pArgs.unshift(_.wrapAsync(callback));
return _.partial.apply(null, pArgs);
},
__: {}, // Identity of placeholder fields
});
@AdrianRossouw
Copy link
Author

I wrote these to use with caolan's async.js.

I was getting annoyed having to make functions for every little thing, like using underscore map in async.waterfall.

Here's an example of it being used :

function rmTree(filepath, next) {
    function _joinPath(f) { return path.join(filepath, f); }

    var recurseFns = [
        async.apply(fs.readdir, filepath),
        _.wrapAsync(_.toArray),
        _.partialAsync(_.map, _.__, _joinPath),
        _.partial(async.forEachSeries, _.__, rmTree),
        async.apply(fs.rmdir, filepath)
    ];

    async.waterfall([
        async.apply(fs.lstat, filepath),
        function(stat, next) {
            if (stat.isFile() || stat.isSymbolicLink()) return fs.unlink(filepath, next);
            if (!stat.isDirectory()) return next(new Error('Unrecognized file.'));
            async.waterfall(recurseFns, next);
        }
    ], next);
}

@AdrianRossouw
Copy link
Author

Here's the version just using async.js, before I added the partial mixins. note all the extra functions created in the recurseFns to do simple processing on it.

function rmTree(filepath, next) {

    function _joinPath(f) { return path.join(filepath, f); }

    var recurseFns = [
        async.apply(fs.readdir, filepath),
        function(files, next) { next(null, _(files).toArray()); },
        function(files, next) { next(null, _(files).map(_joinPath)); },
        function(files, next) { async.forEachSeries(files, rmTree, next); }, 
        async.apply(fs.rmdir, filepath)
    ];

    async.waterfall([
        async.apply(fs.lstat, filepath),
        function(stat, next) {
            if (stat.isFile() || stat.isSymbolicLink()) return fs.unlink(filepath, next);
            if (!stat.isDirectory()) return next(new Error('Unrecognized file.'));
            async.waterfall(recurseFns, next);
        }
    ], next);
}

@AdrianRossouw
Copy link
Author

This is the version using Step I started out with. I don't like how step overloads this and uses it as a callback. Async plays nicely with the node.js standard mechanism for callbacks and error handling.

// Recursive rm.
function rm(filepath, callback) {
    var killswitch = false;
    fs.lstat(filepath, function(err, stat) {
        if (err) return callback(err);
        if (stat.isFile() || stat.isSymbolicLink()) return fs.unlink(filepath, callback);
        if (!stat.isDirectory()) return callback(new Error('Unrecognized file.'));
        Step(function() {
            fs.readdir(filepath, this);
        },
        function(err, files) {
            if (err) throw err;
            if (files.length === 0) return this(null, []);
            var group = this.group();
            _(files).each(function(file) {
                rm(path.join(filepath, file), group());
            });
        },
        function(err) {
            if (err) return callback(err);
            fs.rmdir(filepath, callback);
        });
    });
};

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment