Skip to content

Instantly share code, notes, and snippets.

@rsms
Created February 19, 2010 15:22
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rsms/308779 to your computer and use it in GitHub Desktop.
Save rsms/308779 to your computer and use it in GitHub Desktop.
// closures for node -- wrapping version
var sys = require('sys'), fs = require('fs'), assert = require('assert');
// -------------------------------
// The closure factory
function closure(fun) {
var cl = mkclosure(fun);
if (fun) {
var cargs = Array.prototype.slice.call(arguments, 1);
cargs.push(cl.close);
fun.apply(fun, cargs);
}
return cl;
}
function mkclosure(context){
var c = function(cb){
if (cb.__isClosure) cb = cb.close;
if (c.fired) cb.apply(context || c, c.args || []);
else c.callbacks.push(cb);
return c;
}
if (context) c.context = context;
c.__isClosure = true;
c.callbacks = [];
c.addCallback = c; // alias
c.removeCallback = function(cb) {
c.callbacks = c.callbacks.filter(function(item){ item !== cb; });
return c;
}
c.closev = function(args) {
if (!c.fired) {
if (args) c.args = Array.prototype.slice.call(args);
c.fired = true;
c.callbacks.forEach(function(cb){
cb.apply(context || c, c.args || []);
});
}
return c;
}
c.close = function(/*[err, arg, ..]*/) {
return c.closev(arguments);
}
c.then = function (cb) {
if (!cb || typeof cb !== 'function') {
// if we did not get any there's no callback to chain:
return c;
}
// A new closure which keeps a queue of callbacks
var c2 = mkclosure();
c2.__thenQueue = [cb];
var _closev = c2.closev;
c2.closev = function(args){
if (c2.fired)
return c2;
var nextCb = c2.__thenQueue.shift();
// unroll on error or when the queue is empty
if (nextCb === undefined || args[0]) {
return _closev(args);
}
else {
// slice away the empty error when passing arguments to the next cb
args = Array.prototype.slice.call(args,1);
try {
nextCb.apply(c2.context || c2, args)(c2.close);
}
catch (exc) {
c2.close(exc);
}
}
return c2;
}
c2.then = function(cb) {
if (c2.fired)
cb.apply(c2.context || c2, c2.args || []);
else
c2.__thenQueue.push(cb);
return c2;
}
// Add the closure queue as a listener to the current closure
c(c2);
// return the queueable closure (optimization)
return c2;
}
return c;
}
// -------------------------------
// Patched impls for demo purposes
var fs_stat = fs.stat;
fs.stat = function(fn, cb) {
fs_stat(fn)
.addCallback(function(stats){ cb(null, stats); })
.addErrback(function(err){ cb(err); });
}
var fs_cat = fs.cat;
fs.cat = function(fn, cb) {
fs_cat(fn)
.addCallback(function(data){ cb(null, data); })
.addErrback(function(err){ cb(err); });
}
// -------------------------------
// Example program, reading a file
function readfile(filename, cb) {
if (!cb) {
var stats = fs.statSync(filename);
if (!stats.isFile())
throw new Error('not a file');
return fs.catSync(filename);
}
else {
fs.stat(filename, function(err, stats){
if (err || (!stats.isFile() && (err = new Error('not a file'))))
cb(err);
else
fs.cat(filename, cb);
});
}
}
// synchronous
var data = readfile(__filename);
sys.error('successfully read '+data.length+' bytes.');
// asynchronous
var cl = closure(readfile, __filename)(function(err, data){
if (err) throw err;
else sys.error('successfully read '+data.length+' bytes.');
})
// -------------------------------
// Test then()
function fdirect(arg) {
sys.error('fdirect called');
return closure().close(0, arg);
}
for (var i=5;i;i--) {
eval("function f"+i+"(arg) {\
sys.error('f"+i+" called');\
var cl = closure();"+
(i>1 ? "assert.equal(arg, 'return value from f"+(i-1)+"');" : "")+
"setTimeout(function(){\
sys.error('f"+i+" returning');"+
(i===4?
"cl.close(new Error('thrown in f"+i+"'));"
:
"cl.close(0,'return value from f"+i+"');"
)+
"}, 100);\
return cl;\
}");
}
// This should print calls in sequential order:
// f1 called
// f1 returning
// fdirect called
// f2 called
// ...
// f4 returning
// Error: thrown in f4
// all funs called
cl.then(f1).then(fdirect).then(f2).then(f3).then(f4).then(f5)
(function(err, args){
if (err) sys.error(err);
sys.error('all funs called');
})
// closures for node
var sys = require('sys'), fs = require('fs'), assert = require('assert');
// -------------------------------
// The closure factory
function mkclosure(context){
var c = function(cb){
if (cb.__isClosure) cb = cb.close;
if (c.fired) cb.apply(context || c, c.args || []);
else c.callbacks.push(cb);
return c;
}
if (context) c.context = context;
c.__isClosure = true;
c.callbacks = [];
c.addCallback = c; // alias
c.removeCallback = function(cb) {
c.callbacks = c.callbacks.filter(function(item){ item !== cb; });
return c;
}
c.closev = function(args) {
if (!c.fired) {
if (args) c.args = Array.prototype.slice.call(args);
c.fired = true;
c.callbacks.forEach(function(cb){
cb.apply(context || c, c.args || []);
});
}
return c;
}
c.close = function(/*[err, arg, ..]*/) {
return c.closev(arguments);
}
c.then = function (cb) {
if (!cb || typeof cb !== 'function') {
// if we did not get any there's no callback to chain:
return c;
}
// A new closure which keeps a queue of callbacks
var c2 = mkclosure();
c2.__thenQueue = [cb];
var _closev = c2.closev;
c2.closev = function(args){
if (c2.fired)
return c2;
var nextCb = c2.__thenQueue.shift();
// unroll on error or when the queue is empty
if (nextCb === undefined || args[0]) {
return _closev(args);
}
else {
// slice away the empty error when passing arguments to the next cb
args = Array.prototype.slice.call(args,1);
try {
nextCb.apply(c2.context || c2, args)(c2.close);
}
catch (exc) {
c2.close(exc);
}
}
return c2;
}
c2.then = function(cb) {
if (c2.fired)
cb.apply(c2.context || c2, c2.args || []);
else
c2.__thenQueue.push(cb);
return c2;
}
// Add the closure queue as a listener to the current closure
c(c2);
// return the queueable closure (optimization)
return c2;
}
return c;
}
// -------------------------------
// Wrapped impls for demo purposes
var fs_stat = fs.stat;
fs.stat = function(fn) {
var closure = mkclosure();
fs_stat(fn)
.addCallback(function(stats){ closure.close(null, stats); })
.addErrback(function(err){ closure.close(err); });
return closure;
}
var fs_cat = fs.cat;
fs.cat = function(fn) {
var closure = mkclosure();
fs_cat(fn)
.addCallback(function(data){ closure.close(null, data); })
.addErrback(function(err){ closure.close(err); });
return closure;
}
// -------------------------------
// Example program, reading a file
function readfile(filename) {
var closure = mkclosure();
fs.stat(filename)(function(err, stats){
if (err || (!stats.isFile() && (err = new Error('not a file'))))
closure.close(err);
else
fs.cat(filename)(closure.close);
});
closure.then();
return closure;
}
var cl = readfile(__filename)(function(err, data){
if (err) throw err;
else sys.error('successfully read '+data.length+' bytes.');
})
// -------------------------------
// Test then()
function fdirect(arg) {
sys.error('fdirect called');
return mkclosure().close(0, arg);
}
for (var i=5;i;i--) {
eval("function f"+i+"(arg) {\
sys.error('f"+i+" called');\
var cl = mkclosure();"+
(i>1 ? "assert.equal(arg, 'return value from f"+(i-1)+"');" : "")+
"setTimeout(function(){\
sys.error('f"+i+" returning');"+
(i===4?
"cl.close(new Error('thrown in f"+i+"'));"
:
"cl.close(0,'return value from f"+i+"');"
)+
"}, 100);\
return cl;\
}");
}
// This should print calls in sequential order:
// f1 called
// f1 returning
// fdirect called
// f2 called
// ...
// f4 returning
// Error: thrown in f4
// all funs called
cl.then(f1).then(fdirect).then(f2).then(f3).then(f4).then(f5)
(function(err, args){
if (err) sys.error(err);
sys.error('all funs called');
})
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment