Browse files

Better API coverage, docs and samples.

  • Loading branch information...
1 parent f5041c6 commit 7c1704aadb9495711f229aab84b99b6436b42332 @lm1 committed Jan 24, 2011
View
157 README.md
@@ -0,0 +1,157 @@
+
+INTRODUCTION
+------------
+
+No more callbacks clutter! Fiberize converts Node.js asynchronous API to easy to use and read, straightforward sequential form.
+
+ for (var i = 0; i < 10; ++i) {
+ console.log(i);
+ sleep(500);
+ }
+
+It's possible thanks to fiber based cooperative multithreading added to V8 by [node-fibers](https://github.com/laverdet/node-fibers).
+Fiber is just a thread of execution, but with no preemption, thus safe and simple to use. And of course you may have multiple fibers running in parallel.
+
+GETTING STARTED
+---------------
+
+ npm install fiberize
+
+This will install node-fibers as well. Working g++, node headers and boost library are required. You need to rebuild node-fibers each time you upgrade node!
+
+Then run your code with:
+
+ node-fibers your_file.js
+
+EXAMPLES
+--------
+
+Fully featured example from introduction:
+
+ var fiberize = require('fiberize');
+
+ fiberize.start(function() {
+ console.log('Hello');
+ fiberize.sleep(1000);
+ console.log('World');
+ });
+
+This following examples shows how the original API is extended by fiberize (notice W suffix):
+
+ var fiberize = require('fiberize');
+ var fs = fiberize.require('fs');
+
+ function tree(p) {
+ var files = fs.readdirW(p);
+ for (var i = 0; i < files.length; ++i) {
+ var info = fs.statW(p + files[i]);
+ console.log(p + files[i]);
+ if (info.isDirectory()) {
+ tree(p + files[i] + '/');
+ }
+ }
+ }
+
+ fiberize.start(tree, process.cwd() + '/');
+
+Although Node provides fs.readdirSync and fs.statSync calls, the above will not block the entire process, thus all other Fibers can execute while this one is waiting for the data.
+
+You can serve each HTTP request with separate fiber. You can also use exceptions unlike with callbacks.
+
+ var fiberize = require('fiberize');
+ var dns = fiberize.require('dns');
+ var http = require('http');
+ var url = require('url');
+
+ http.createServer(fiberize.task(function(req, res) {
+ var addr = url.parse(req.url, true).query.addr;
+ res.writeHead(200);
+ if (!addr) {
+ res.end('<form method=get>Address to resolve:' +
+ '<input type=text name=addr><input type=submit></form>');
+ } else {
+ try {
+ res.end(addr + ' resolves to ' + dns.resolveW(addr));
+ } catch (e) {
+ res.end(e.message || 'Error');
+ }
+ }
+ })).listen(8000);
+ console.log('Listening on port 8000...');
+
+
+INTERFACE
+---------
+
+ var fiberize = require('fiberize');
+
+- fiberize(obj)
+
+fiberize itself is a function which extends given object *obj* so for all functions which explicitly declare last argument named callback, a new function with suffix 'W' is added.
+
+The new funcion does take same parameters with exception of callback, and behaves the same, but does not return immediately. Instead it suspends current fiber until the underlying callback is triggered and returns the value passed as second argument to callback. If the first argument (usuall err) was given to the callback exception is thrown (see below for more details), e.g.:
+
+ var obj = {
+ method1: function(a, callback) {}
+ };
+
+*fiberize(obj)* will extend obj as follows:
+
+ {
+ method1: function(a, callback) {},
+ method1W: function(a) {}
+ }
+
+*You cannot use transformed functions directly from main thread, you need to create a fiber first.*
+
+- fiberize.require(path)
+
+For convenience fiberize.require function has been provided, does the same as *require*, but extends the required module by the way.
+
+- fiberize.start(f /* , args... */ )
+
+*start* runs *f* in a new fiber and passes *args* to *f*.
+Returns the value returned by *f*.
+
+- fiberize.task(f)
+
+Returns a function which will execute *f* in new fiber upon invocation. All the arguments of the returned function are passed directly to *f*. Useful to postpone start of a fiber, or wrap a callback in a fiber.
+
+- fiberize.sleep(ms)
+
+Suspends current fiber for *ms* miliseconds. Doesn't block the event loop, thus other fibers may execute.
+
+CONVENTIONS
+-----------
+
+Fiberaze bases strongly on consistent conventions used in Node API. If you're using third party libraries following similar conventions you may be able to transform them as well.
+
+Basically all functions receiving callback as the very last arguments get additional form suffixed with 'W'. The callback must be specified explicitly in the arguments list (also comments are parsed).
+
+The return value of new functions depends on behavior of original function, that is for functions which:
+
+- do not return a value and pass 1 argument to the callback
+> if callback 1st argument evaluates to true it's thrown as exception
+- do not return a value and pass 2 arguments to the callback
+> if callback 1st argument evaluates to true it's thrown as exception,
+> otherwise 2nd callback argument is returned
+- do not return a value and pass 3 or more arguments to the callback
+> if callback 1st argument evaluates to true it's thrown as exception,
+> otherwise array containing 2nd and the following callback arguments is returned
+- do return a value (other than this)
+> returns an array containing original result followed by all callback arguments
+
+*Some special cases are handled differently.*
+
+WARNING
+-------
+
+This module is at initial stage of development. Though large part of the Node core API is covered (with tests), not all modules can be fiberized automatically.
+
+Namely events, net, stream, and HTTP are inherently asynchronous and require use of callbacks.
+
+
+COMMING SOON
+------------
+
+P like Promises (aka futures).
View
32 examples/http_serv.js
@@ -0,0 +1,32 @@
+var fiberize = require('fiberize');
+var fs = fiberize.require('fs');
+var path = fiberize.require('path');
+var http = require('http');
+var url = require('url');
+
+var cwd = process.cwd() + '/';
+
+http.createServer(fiberize.task(function(req, res) {
+ var p = url.parse(req.url).pathname.slice(1);
+ var rp = path.resolve(cwd, p);
+ console.log('Request /' + p);
+ try {
+ var info = fs.statW(rp);
+ if (info.isFile()) {
+ res.writeHead(200);
+ res.end(fs.readFileW(rp, 'utf8'));
+ } else if (info.isDirectory()) {
+ var list = fs.readdirW(rp);
+ res.writeHead(200);
+ list.forEach(function(f) {
+ res.write(f.link(p + '/' + f) + '<br>\n');
+ });
+ res.end();
+ }
+ } catch (e) {
+ console.error(e.message || e);
+ res.writeHead(404);
+ res.end('File not found');
+ }
+})).listen(8000);
+console.log('Listening on port 8000...');
View
22 examples/listfiles.js
@@ -0,0 +1,22 @@
+var fiberize = require('fiberize');
+var fs = fiberize.require('fs');
+
+fiberize.start(function() {
+
+ var files = fs.readdirW(process.cwd());
+
+ var total_bytes = 0;
+ var total_lines = 0;
+
+ for (var i = 0; i < files.length; ++i) {
+ var info = fs.statW(files[i]);
+ if (info.isFile()) {
+ var lines = fs.readFileW(files[i], 'utf8').split('\n').length;
+ console.log(files[i], 'is', info.size, 'bytes', lines, 'lines');
+ total_bytes += info.size;
+ total_lines += lines;
+ }
+ console.log('Total:', total_bytes, 'bytes', total_lines, 'lines');
+ }
+
+});
View
10 examples/seq1.js
@@ -0,0 +1,10 @@
+var fiberize = require('fiberize');
+
+fiberize.start(function() {
+
+ for (var i = 0; i < 10; ++i) {
+ console.log(i);
+ fiberize.sleep(500);
+ }
+
+});
View
16 examples/tree.js
@@ -0,0 +1,16 @@
+var fiberize = require('fiberize');
+var fs = fiberize.require('fs');
+
+function tree(p) {
+ var files = fs.readdirW(p);
+ for (var i = 0; i < files.length; ++i) {
+ var info = fs.statW(p + files[i]);
+ console.log(p + files[i]);
+ if (info.isDirectory()) {
+ tree(p + files[i] + '/');
+ }
+ }
+}
+
+fiberize.start(tree, process.cwd() + '/');
+
View
20 examples/web_dns.js
@@ -0,0 +1,20 @@
+var fiberize = require('fiberize');
+var dns = fiberize.require('dns');
+var http = require('http');
+var url = require('url');
+
+http.createServer(fiberize.task(function(req, res) {
+ var addr = url.parse(req.url, true).query.addr;
+ res.writeHead(200);
+ if (!addr) {
+ res.end('<form method=get>Address to resolve:' +
+ '<input type=text name=addr><input type=submit></form>');
+ } else {
+ try {
+ res.end(addr + ' resolves to ' + dns.resolveW(addr));
+ } catch (e) {
+ res.end(e.message || 'Error');
+ }
+ }
+})).listen(8000);
+console.log('Listening on port 8000...');
View
56 fiberize.js 100755 → 100644
@@ -24,30 +24,30 @@ require('fibers');
module.exports = fiberize;
-module.exports.wrapf = wrapf;
-
module.exports.require = function(path) {
return fiberize(require(path));
};
-function build_result(result, cb_args) {
+function build_result(result, cb_args, type) {
var ret = {};
if (result !== undefined) {
ret.value = [result].concat(cb_args);
+ } else if (type === 0) {
+ ret.value = cb_args;
} else {
if (cb_args[0]) {
ret.error = cb_args[0];
+ return ret;
} else {
ret.value = cb_args.slice(1);
- if (ret.value.length == 1) ret.value = ret.value[0];
}
}
+ if (ret.value.length == 1) ret.value = ret.value[0];
return ret;
}
-function wrapf(fn) {
+function wrapf(fn, type) {
return function() {
- var self = this;
var args = Array.prototype.slice.call(arguments);
var fiber = Fiber.current;
var returned = false;
@@ -56,7 +56,7 @@ function wrapf(fn) {
var cb = function(err) {
cb_args = Array.prototype.slice.call(arguments);
if (!returned) return true;
- var ret = build_result(result, cb_args);
+ var ret = build_result(result, cb_args, type);
if (ret.error) {
fiber.throwInto(ret.error);
return false;
@@ -65,58 +65,57 @@ function wrapf(fn) {
return true;
}
};
- var len = fn.__length || fn.length;
- if (args.length >= len) args.push(cb);
- else args[len - 1] = cb;
- var value = fn.apply(self, args);
+ args.push(cb);
+ var value = fn.apply(this, args);
returned = true;
- if (value !== self) result = value;
+ if (value !== this) result = value;
if (cb_args) {
- var ret = build_result(result, cb_args);
+ var ret = build_result(result, cb_args, type);
if (ret.error) throw (ret.error);
else return ret.value;
}
return yield();
}
}
-function fiberizeObject(obj) {
+function fiberize(obj) {
if (obj.__fiberized) return obj;
for (var f in obj) {
var fn = obj[f];
- console.log('Found:', f, typeof fn);
if (typeof fn !== 'function' || /^_.*|.*_$/.test(f)) continue;
-
- var body = obj[f].toString(); // stringify function body
- // extract arguments, also the commented ones
+ var body = fn.toString(); // stringify function body
+ // extract arguments (including the commented ones)
var args = /function [\w$]*\((.*)\)\s*{/.exec(body)[1]
.replace(/\/\*|\*\//g, ', ')
.replace(/\s*/g, '')
.replace(/,,/g, ',')
.replace(/,$/, '')
.split(',');
var lastarg = args.slice(-1).toString();
- if (lastarg == 'callback' || obj[f].__hasCallback) {
- console.log('Wrapping', f, '(', args, ')');
- fn.__length = args.length;
+ if (/^cb$|callback/.test(lastarg) || fn.__hasCallback) {
if (obj[f + 'W'] === undefined) {
- obj[f + 'W'] = wrapf(fn);
+ obj[f + 'W'] = wrapf(fn, fn.__callbackType);
} else {
console.error('(fiberize) Warning:', f + 'W', 'already defined.');
}
}
- if (fn.prototype) fiberizeObject(fn.prototype);
+ if (fn.prototype) fiberize(fn.prototype);
}
+ if (obj.prototype) fiberize(obj.prototype);
obj.__fiberized = true;
return obj;
}
-function fiberize(mod, proto) {
- return fiberizeObject(mod, proto);
-}
-
module.exports.start = function(f) {
- return Fiber(f).run();
+ return Fiber(function(args) {
+ return f.apply(this, args);
+ }).run(Array.prototype.slice.call(arguments, 1));
+};
+
+module.exports.task = function(f) {
+ return function() {
+ Fiber(function(args) {f.apply(this, args);}).run(arguments);
+ };
};
module.exports.sleep = function(ms) {
@@ -126,3 +125,4 @@ module.exports.sleep = function(ms) {
};
require('fs').readFile.__hasCallback = true;
+require('path').exists.__callbackType = 0;
View
7 package.json 100755 → 100644
@@ -1,11 +1,14 @@
{
"name": "fiberize",
- "description": "Node API wrapper for node-fibers.",
- "version": "0.0.0",
+ "description": "Node API wrapper for use with node-fibers.",
+ "version": "0.0.1",
"url": "http://github.com/lm1/node-fiberize",
"author": "Lukasz Mielicki <mielicki@gmail.com>",
"main": "fiberize",
"engines": {
"node": ">=0.3.6"
+ },
+ "dependencies" : {
+ "fibers": ">=0.1.0"
}
}
View
34 test/callback_args_test.js 100755 → 100644
@@ -57,7 +57,7 @@ mod.fr0 = function(callback) {
callback();
return 1;
};
-mod.fr0.result = [1];
+mod.fr0.result = 1;
mod.fr1 = function(a, callback) {
callback(a);
@@ -66,22 +66,42 @@ mod.fr1 = function(a, callback) {
mod.fr1.args = [2];
mod.fr1.result = [1, 2];
+mod.fr2 = function(a, b, callback) {
+ callback(a, b);
+ return 1;
+};
+mod.fr2.args = [2, 3];
+mod.fr2.result = [1, 2, 3];
+
+mod.frerr = function(callback) {
+ callback(true);
+ return 1;
+};
+mod.frerr.result = [1, true];
+
//
// Delayed callback with result
//
-mod.fr1d = function(callback) {
- process.nextTick(function() {callback(2);});
+mod.fr0d = function(callback) {
+ process.nextTick(function() {callback();});
+ return 1;
+};
+mod.fr0d.result = 1;
+
+mod.fr1d = function(a, callback) {
+ process.nextTick(function() {callback(a);});
return 1;
};
+mod.fr1d.args = [2];
mod.fr1d.result = [1, 2];
-mod.fr2d = function(callback) {
- process.nextTick(function() {callback(2, 3);});
+mod.fr2d = function(a, b, callback) {
+ process.nextTick(function() {callback(a, b);});
return 1;
};
+mod.fr2d.args = [2, 3];
mod.fr2d.result = [1, 2, 3];
-
fiberize(mod);
fiberize.start(function() {
@@ -92,7 +112,7 @@ fiberize.start(function() {
console.log(fn, f.args, f.result, f.throws ? 'throws' : '');
- var args = f.args || [];
+ var args = f.args; // || undefined
var result;
if (f.throws) {
assert.throws(function() {
View
2 test/childp_test.js 100755 → 100644
@@ -11,7 +11,7 @@ fiberize.start(function() {
// result[0] is a returned child object
assert.equal(typeof result[0].kill, 'function');
- // result[1] is 1st callabck argument (error code)
+ // result[1] is 1st callback argument (error code)
assert.equal(result[1], null);
// result[2] is 2ns callback argument (stdout)
View
14 test/dns_test.js
@@ -0,0 +1,14 @@
+var fiberize = require('fiberize');
+var dns = fiberize.require('dns');
+var assert = require('assert');
+
+fiberize.start(function() {
+
+ assert.deepEqual(dns.lookupW('localhost'), ['127.0.0.1', 4]);
+
+ assert.deepEqual(dns.resolveW('localhost'), ['127.0.0.1']);
+
+ //assert.equal(dns.reverseW('66.102.13.147'), 'ez-in-f147.1e100.net');
+
+ process.nextTick(function() { console.log('_END_');});
+});
View
18 test/fs_test.js 100755 → 100644
@@ -15,6 +15,24 @@ fiberize.start(function() {
var txt = fs.readFileW(file, 'utf8');
assert.ok(txt.length == info.size);
+ var data = fs.readFileW(file);
+ assert.ok(data.length == info.size);
+
+ assert.throws(function() {
+ fs.unlinkW('^$%#$%$#@^%');
+ });
+
+ var cwd = process.cwd();
+ var rel = __filename.slice(cwd.length + 1);
+ var dir = __filename.split('/').slice(0, -1).join('/');
+ var fn = __filename.split('/').pop();
+
+ var p = fs.realpathW(rel);
+ assert.equal(__filename, p);
+
+ var res = fs.readdirW(dir);
+ assert.ok(res.some(function(f) { return f === fn; }));
+
assert.throws(function() {
fs.writeFileW('.', 'ABC', 'utf8');
});
View
11 test/path_test.js
@@ -0,0 +1,11 @@
+var fiberize = require('fiberize');
+var path = fiberize.require('path');
+var assert = require('assert').ok;
+
+fiberize.start(function() {
+
+ assert(path.existsW(__filename));
+ assert(path.existsW('@#$#^^&') == false);
+
+ process.nextTick(function() { console.log('_END_');});
+});
View
0 test/proto_test.js 100755 → 100644
File mode changed.
View
17 test/pump_test.js
@@ -0,0 +1,17 @@
+var fiberize = require('fiberize');
+var child_process = require('child_process');
+var util = fiberize.require('util');
+var assert = require('assert');
+
+fiberize.start(function() {
+
+ var child = child_process.spawn('ls');
+
+ // pump ends output stream, need to prevent that
+ // to be able to use it later
+ process.stdout.end = function() {};
+
+ util.pumpW(child.stdout, process.stdout);
+
+ process.nextTick(function() { console.log('_END_');});
+});
View
0 test/sleep_test.js 100755 → 100644
File mode changed.
View
0 test/this_test.js 100755 → 100644
File mode changed.

0 comments on commit 7c1704a

Please sign in to comment.