-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Are async handlers on the roadmap? #141
Comments
One approach would be to use metamorph.js, which we use for something like this in SproutCore: // simple example
SC.registerHelper('async', function(text) {
var morph = Metamorph("");
setTimeout(function() {
morph.html(text);
}, 1000);
return morph.outerHTML();
}); |
I'm going to close this. If you have additional use-cases that aren't covered by this solution, please reopen with more information. |
metamorph.js will not work when using handlebars in server-side nodejs, right? |
The idea is that the helper is making async I/O while rendering? I don't quite understand why you would want to do this. |
+1. A made-up [probably non-working] example in CoffeeScript: myTemplate = """
{{render_user userId}}
"""
handlebars.registerAsyncHelper "render_user", (userId, callback) ->
mongo["users"].find(userId).end (data) ->
if !data?
callback(handlebars.compile(handlebars.partials["no_user"])())
else
callback(handlebars.compile(handlebars.partials["show_user"])(userId))
render = (req, res, next) ->
template = handlebars.compile(myTemplate)
template userId, (html) ->
res.send html I think hbs (https://github.com/donpark/hbs) has a registerAsyncHelper but I don't know how one can one use it, as the render is sync; there's definitely no examples on people using it. |
I have a use case for asynchronous helper. I'm using a node library that asynchronously parses html and builds a table of contents. I would like my helper to return the TOC but I can't because the helper is synchronous. |
Seriously? Because the helper could do all kinds of things in Nodeland that are asynchronous, like almost everything we do in Node. ...Including non-blocking I/O. For example to read, parse and include a Markdown file. The request that starts triggers rendering could be one of many, many concurrent requests. There's no reason to block all other requests that could be doing their thing just so Handlebars can hog the process while rendering a single view. Please correct me if I'm wrong... |
Interestingly, the only async template engine I know of is dust.js. Jade had the foresight to implement an async api (that's all it has), but the actually library is synchronous. |
+1 it would be nice to have it use the ecma script 6 "yield generators" so it could still be written in a synchronous style |
Looking at the code from https://github.com/donpark/hbs It seems that an async helper is just an helper returning an unique id string that will be rendered synchronously, while the same id is assigned to a promise pending until the helper finishes its actual work. After synchronous render ends and all promises are fullyfied each id is replaced with promises result. I can't see how they manage nested async helpers like that, neither I know if this actually works. Obviously to have the correct output the rendering function needs to invoke a callback or return a promise itself. The code below is an excerpt from hbs code: var async = require('./async');
// ...
Instance.prototype.registerAsyncHelper = function(name, fn) {
this.handlebars.registerHelper(name, function(context) {
return async.resolve(fn, context);
});
};
// ... async.done(function(values) {
Object.keys(values).forEach(function(id) {
res = res.replace(id, values[id]);
});
cb(null, res);
}); /// async.js
/// provides the async helper functionality
// global baton which contains the current
// set of deferreds
var waiter;
function Waiter() {
var self = this;
// found values
self.values = {};
// callback when done
self.callback = null;
self.resolved = false;
self.count = 0;
};
Waiter.prototype.wait = function() {
var self = this;
++self.count;
};
// resolve the promise
Waiter.prototype.resolve = function(name, val) {
var self = this;
self.values[name] = val;
// done with all items
if (--self.count === 0) {
self.resolved = true;
// we may not have a done callback yet
if (self.callback) {
self.callback(self.values);
}
}
};
// sets the done callback for the waiter
// notifies when the promise is complete
Waiter.prototype.done = function(fn) {
var self = this;
self.callback = fn;
if (self.resolved) {
fn(self.values);
}
};
// callback fn when all async helpers have finished running
// if there were no async helpers, then it will callback right away
Waiter.done = function(fn) {
// no async things called
if (!waiter) {
return fn({});
}
waiter.done(fn);
// clear the waiter for the next template
waiter = undefined;
};
Waiter.resolve = function(fn, context) {
// we want to do async things, need a waiter for that
if (!waiter) {
waiter = new Waiter();
}
var id = '__' + gen_id() + '__';
var cur_waiter = waiter;
waiter.wait();
fn(context, function(res) {
cur_waiter.resolve(id, res);
});
// return the id placeholder
// this will be replaced later
return id;
};
var alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_';
var gen_id = function() {
var res = '';
for (var i=0 ; i<8 ; ++i) {
res += alphabet[Math.floor(Math.random() * alphabet.length)];
}
return res;
};
module.exports = Waiter; |
I'd love to be able to register handlers that run async functions and have the results of those functions be rendered into the template. There aren't very many options for that right now, but dust and asyncEJS both have interesting mechanisms that support it. Is this anywhere on the radar or is it just totally out of scope for Handlebars?
felix
The text was updated successfully, but these errors were encountered: