Basic idea of asynchronous template processing. #450

Closed
wants to merge 1 commit into
from

Conversation

Projects
None yet
2 participants
@ghost

ghost commented Feb 24, 2013

Async template processing

Actually the implementation isn't finished.

By creating an issue it's not possible to add a commit of the implementation idea.
So I use this pull request for starting a discussion.

Why?

Accessing additional data for template helpers or functions in the receiving template data is often asynchronous, especially in client side (in browser) template processing, it's hard to get all data access synchronous.

How to?

Use array buffer instead of string

I know using an array and join the strings decreases the performance, that's why async should be optional for the processing and compiling as well. So only async compiled templates are able to process async.

So we don't decrease the performance of default sync template processing.

Additional createCallback param

Each template helper and data of type function get an additional param createCallback. If the on the call method want to process async, it's required to call immediately the createCallback() method, this returns a callback method. For finishing the async helper process just call callback(result) with the results as first param.

Would this be geared more for precompilation or for in-browser template parsing?

@ghost

ghost commented Feb 27, 2013

It's geared for both.

If I want to process the template async, for example I've an async template helper, I need to (pre)compile (server/client) the the template for async mode.

I added method .compileAsync() and .precompileAsync, in these cases the compiled code uses an array buffer, so it's possible to process async.

I want to touch the normal compiling and processing as less as possible.

Short example how it's work:

// Register helper
Handlebars.registerHelper("my-helper", function (value, options, createCallback) {
  // Exists only if compiled for async processing
  if (createCallback) {
    // Get a callback method
    var done = createCallback();

    // Async load of a special module
    require(["my-special-module"], function (mySpecialModule) {
      // Set value in template by callback
      done(mySpecialModule.handle(value));
    });
  }
  else {
    return "No result, async compiled template required!";
  }
});

// Compile for async mode, could be precompiled as well
var template = Handlebars.compileAsync(" ... {{my-helper name}} ... ");

// Process template
template({name: "Evan"}, function (html) {
  // Processing done
});
Collaborator

kpdecker commented Feb 27, 2013

I have a couple of concerns with this:

  1. How does this interact with existing sync helpers and evaluated context methods?
  2. The source files that generate the dists need to be updated not just the dists. This is needed both so the changes aren't lost and so node can exec the changes.
  3. We will need tests for this specific case as it's pretty sweeping and from the example above I want to make sure that everything still functions properly. In particular non-async helpers etc in async mode.
  4. This makes it very unclear what the meaning of the return value from a helper vs. the callback provided value.

I wasn't able to run the code above in my test env (node) but the non-async version generates

if (stack1 = helpers.foo) { stack1 = stack1.call(depth0, options); }
else { stack1 = depth0.foo; stack1 = typeof stack1 === functionType ? stack1.apply(depth0) : stack1; }
if (!helpers.foo) { stack1 = blockHelperMissing.call(depth0, stack1, options); }

And I believe the async will be something like

if (stack1 = helpers.foo) { stack1 = stack1.call(depth0, options, callback); }
else { stack1 = depth0.foo; stack1 = typeof stack1 === functionType ? stack1.apply(depth0, callback) : stack1; }
if (!helpers.foo) { stack1 = blockHelperMissing.call(depth0, stack1, options, callback); }

Note how stack1 is returned and passed on to blockHelperMissing for the case where foo is not defined as a helper but is defined as a function on the context.

@ghost

ghost commented Feb 27, 2013

I already know, just editing the dist files isn't enough and this pull request should NOT be merged.
I just want to discuss about this way of the implementation, a lot of work is still to do.

@kpdecker You believe the async will be something like

if (stack1 = helpers.foo) { stack1 = stack1.call(depth0, options, callback); }
else { stack1 = depth0.foo; stack1 = typeof stack1 === functionType ? stack1.apply(depth0, callback) : stack1; }
if (!helpers.foo) { stack1 = blockHelperMissing.call(depth0, stack1, options, callback); }

it will be this (quite similar)

if (stack1 = helpers.foo) { stack1 = stack1.call(depth0, options, createCallback); }
else { stack1 = depth0.foo; stack1 = typeof stack1 === functionType ? stack1.apply(depth0, createCallback) : stack1; }
if (!helpers.foo) { stack1 = blockHelperMissing.call(depth0, stack1, options, createCallback); }

The async helper needs to call var callback = createCallback(); any existing synchronous helper will work as expected "sync". createCallback creates and registers a callback for your helper and this callback knows which item of the buffer array needs to be replaced.

Collaborator

kpdecker commented Jun 2, 2013

While this is interesting feature and a smart implementation I don't think that we want to bring this in as it adds performance overhead and it feels like it could get users in trouble with the added mental overhead of managing async and sync inside of the template.

@kpdecker kpdecker closed this Jun 2, 2013

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