Skip to content

Instantly share code, notes, and snippets.

@creationix
Created July 9, 2010 01:12
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 creationix/468889 to your computer and use it in GitHub Desktop.
Save creationix/468889 to your computer and use it in GitHub Desktop.
Async Compiler
var compiler = require('./asyncCompiler'),
fs = require('fs');
compiler.helpers = {
partial: function (filename, data, callback) {
fs.readFile(filename, function (err, text) {
if (err) { callback(err); return; }
compiler(text, data, callback);
});
}
}
var template = "Hello @planet, my name is @name() and I am @age() years old.";
var data = {
// Value
planet: "world",
// Async helper
name: function name(callback) {
process.nextTick(function () {
callback(null, "Tim Caswell");
});
},
// Sync helper
age: function age() {
return 28;
}
};
// Get the compiled template, and the execute it
var fn = compiler(template);
console.log("\nJavaScript");
console.log(fn.toString());
fn(data, function (err, text) {
console.log("OUTPUT " + text);
// Or do it in one shot
compiler(template, data, function (err, text) {
console.log("OUTPUT2 " + text);
// Or use a stream
var stream = new process.EventEmitter();
fn(data, stream);
stream.addListener('data', function (data) {
console.log("DATA " + JSON.stringify(data.toString()));
});
stream.addListener('end', function () {
console.log("END");
});
stream.addListener('error', function (err) {
console.log("ERROR " + err.stack);
});
});
});
JavaScript
function fn(locals, callback) {
locals.__proto__ = module.exports.helpers;
var position = 0, stream, chunks;
if (!(typeof callback === 'function')) {
if (callback instanceof process.EventEmitter) {
stream = callback;
callback = undefined;
} else {
throw new Error("Type Error: last argument must be either a callback function or stream instance");
}
}
function check() {
var pieces = [];
while (chunks[position] && position < chunks.length) {
if (stream) {
pieces.push(chunks[position]);
}
position++;
}
if (stream && pieces.length > 0) {
stream.emit('data', new Buffer(pieces.join("")));
}
if (position === chunks.length) {
if (callback) {
callback(null, new Buffer(chunks.join("")));
}
if (stream) {
stream.emit('end');
}
}
}
function execute(position, fn) {
try {
chunks[position] = fn(function (err, result) {
if (err) {
if (stream) stream.emit('error', err);
if (callback) callback(err);
return;
}
chunks[position] = result;
check();
});
} catch (err) {
if (stream) stream.emit('error', err);
if (callback) callback(err);
}
}
process.nextTick(function () {
with(locals) {
// START GENERATED CODE
chunks = new Array(5);
chunks[0] = "Hello " + planet + ", my name is ";
execute(1, name);
chunks[2] = " and I am ";
execute(3, age);
chunks[4] = " years old";
check();
// END GENERATED CODE
}
});
}
OUTPUT Hello world, my name is Tim Caswell and I am 28 years old
OUTPUT2 Hello world, my name is Tim Caswell and I am 28 years old
DATA "Hello world, my name is "
DATA "Tim Caswell and I am 28 years old"
END
@tj
Copy link

tj commented Jul 9, 2010

--pending || callback(null, chunks.join('')); is the idiom I use, nice and simple. I dont see much point for many cases with async views though. You view waits on pending data anyways so it is not much different than pending before passing some locals to a view

@creationix
Copy link
Author

there are tons of use cases for async views, especially in node (think partials) where all I/O is required to be async.

Also, I'm working on a streaming version that emits chunks as they are ready. This is great for landing pages where you want a fast initial load, but some data takes a while to compute.

@guybrush
Copy link

guybrush commented Jul 9, 2010

http://github.com/raycmorgan/Mu supports async parsing/compiling and is based on Mustache

@tj
Copy link

tj commented Jul 9, 2010

its easy to do partials without async, IMO (unless your reloading) you should not need to do any IO for views. I benchmarked Mu vs haml.js and they are nearly identical. Im not against the idea, I just dont think it has much relevance for 99% of views

@tj
Copy link

tj commented Jul 9, 2010

the chunking thing would be nice in some cases, but most user agents I have seen dont even output the chunks as they receive them, at least most browsers cant render

@tj
Copy link

tj commented Jul 9, 2010

if you wanted that though I suppose even with existing stuff like haml or jade, you could change the generated buf.push() calls to res.write(). dont get me wrong though im totally not against the idea, but until I have a practical need for it myself it is not worth implementing. I can see this being more useful for maybe feed generation or something along those lines where you could pipe the data through

@creationix
Copy link
Author

Updated API per bradleymeck's suggestions on IRC

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