Created
February 19, 2010 15:22
-
-
Save rsms/308779 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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'); | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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