Skip to content

Instantly share code, notes, and snippets.

@creationix
Created December 27, 2011 17:56
Show Gist options
  • Save creationix/1524578 to your computer and use it in GitHub Desktop.
Save creationix/1524578 to your computer and use it in GitHub Desktop.
Request for Comments on new API for Step
module.exports = TwoStep;
var slice = Array.prototype.slice;
function Group(callback) {
this.args = [null];
this.left = 0;
this.callback = callback;
this.isDone = false;
}
Group.prototype.done = function done() {
if (this.isDone) return;
this.isDone = true;
this.callback.apply(null, this.args);
};
Group.prototype.error = function error(err) {
if (this.isDone) return;
this.isDone = true;
var callback = this.callback;
callback(err);
};
// Simple utility for passing a sync value to the next step.
Group.prototype.pass = function pass() {
var values = slice.call(arguments);
for (var i = 0, l = values.length; i < l; i++) {
this.args.push(values[i]);
}
};
// Register a slot in the next step and return a callback
Group.prototype.slot = function slot() {
var group = this;
var index = group.args.length;
group.args.length++;
group.left++;
return function (err, data) {
if (err) return group.error(err);
group.args[index] = data;
if (--group.left === 0) group.done();
};
}
// Creates a nested group where several callbacks go into a single array.
Group.prototype.makeGroup = function makeGroup() {
var group = this;
var index = this.args.length;
this.args.length++;
group.left++;
return new Group(function (err) {
if (err) return group.error(err);
var data = slice.call(arguments, 1);
group.args[index] = data;
if (--group.left === 0) group.done();
});
};
// Expose just for fun and extensibility
TwoStep.Group = Group;
// Stepper function
function exec(steps, args, callback) {
var pos = 0;
next.apply(null, args);
function next() {
var step = steps[pos++];
if (!step) {
callback && callback.apply(null, arguments);
return;
}
var group = new Group(next);
step.apply(group, arguments);
if (group.left === 0) group.done();
}
}
// Execute steps immedietly
function TwoStep() {
exec(slice.call(arguments), []);
}
// Create a composite function with steps built-in
TwoStep.fn = function () {
var steps = slice.call(arguments);
return function () {
var args = slice.call(arguments);
var callback = args.pop();
exec(steps, args, callback);
};
}
var TwoStep = require('./twostep');
var FS = require('fs');
var Path = require('path');
// Create a composite function using TwoStep.fn
var statdir = TwoStep.fn(
function (directory) {
this.pass(directory);
FS.readdir(directory, this.slot());
},
function (err, directory, fileNames) {
if (err) return this.error(err);
this.pass(directory, fileNames);
var group = this.makeGroup();
fileNames.forEach(function (name) {
FS.stat(name, group.slot());
});
},
function (err, directory, filenames, stats) {
if (err) return this.error(err);
var output = {};
filenames.forEach(function (name, i) {
var path = Path.join(directory, name);
output[path] = stats[i];
});
this.pass(output);
}
);
statdir(__dirname, function (err, stats) {
if (err) throw err;
console.log("Stats", stats);
})
var TwoStep = require('./twostep');
var FS = require('fs');
TwoStep(
function one() {
this.pass(__filename + ".bak");
FS.readFile(__filename, 'utf8', this.slot());
},
function two(err, target, contents) {
if (err) throw err;
this.pass(target);
FS.writeFile(target, contents, this.slot())
},
function three(err, target) {
if (err) throw err;
console.log("%s written to successfully", target);
FS.readdir(__dirname, this.slot());
},
function four(err, fileNames) {
if (err) throw err;
this.pass(fileNames);
var group = this.makeGroup();
fileNames.forEach(function (filename) {
FS.stat(filename, group.slot());
});
},
function five(err, fileNames, stats) {
if (err) throw err;
this.pass(fileNames.filter(function (name, i) {
return stats[i].isFile();
}));
var group = this.makeGroup();
stats.forEach(function (stat, i) {
if (stat.isFile()) FS.readFile(fileNames[i], 'utf8', group.slot());
});
},
function six(err, fileNames, contents) {
if (err) throw err;
var merged = {};
fileNames.forEach(function (name, i) {
merged[name] = contents[i].substr(0, 80);
});
console.log(merged);
}
);
@stereobooster
Copy link

My attemp (I ported test from Step, but it doesn't pass all yet). Step is anyway much simpler and elegant then async. It covers 80% of use cases. For thoes 20% we can expand API if it suits.

@mikehenrty
Copy link

We (@GameClosure) loved the ideas behind this gist, so we made our own "fork" of it called FF. We expanded on TwoStep by adding immediate failure and success calls (skipping the rest of the function chain), and we made the return object promise compatible. We are actively maintaining it currently, so feedback is highly welcomed.

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