-
Notifications
You must be signed in to change notification settings - Fork 18
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
Async support? #18
Comments
Interesting. So you could have blocks that run async with a callback and when complete their return value could render to the location in the template where the block is defined. Or the block could be passed a reference to an object it could render to and the rendered content would be inserted where the block is in the template. Would it look something like?:
Need to mull on it a bit to see if there would be a straightforward path. |
Well, I'd personally prefer promise support over callback support for Teacup, if asynchronous templating is to be made possible. Specifically, I'd like Teacup to be able to operate asynchronously such that any promises passed as arguments into tag functions are resolved before trying to render that tag. The tag functions would need to detect when they receive promise arguments (identified as objects with a # base case
div somePromiseProducingAsyncCall()
# leaves a block open inside the <div>
# when the promise is fulfilled its value is used to fill in that block
# equivalent to above suggested callback-style:
raw (done) -> # I think raw is needed, since there needs to be *something* providing the done callback?
someCallbackTakingAsyncCall (err, res) ->
done err, -> div res The above base case is definitely possible. I'd also like for tag functions to work when called inside a # should allow tag functions inside a then, like this:
div someAsyncCall().then (res) ->
span "Got async result: #{res}"
# may require that 'then' wraps the tag func call in a function
# since then Teacup may choose when to call the function and what context to use, etc.
# would look like this:
div someAsyncCall().then (res) ->
-> span "Got async result: #{res}"
# functionally equivalent to first callback sample, shown again below
div (done) ->
someAsyncCall (err, res) ->
done err, ->
span "Got async result: #{res}" Edit: On further thought, I think
# if there are any promises used inside a template
# then calling it returns a promise for the rendered HTML instead of a string
template = renderable ->
div somePromise
template().then (html) ->
res.send html Of course, standard callbacks are rather more ubiquitous than promises, so you might prefer to support the former. Although if you do add either kind of async support, supporting both isn't actually that complicated, since the basic "reserve space in this block for stuff we're waiting on" code will be identical. You could even do this: if Q.isPromise block # we have a promise
promise = block
else if _.isFunction block and block.length is 1 # we have a callback async block
deferred = Q.defer()
block deferred.makeNodeResolver()
promise = deferred.promise
# work with async stuff; we now only have promises and no callbacks |
What about using IcedCoffeeScript instead of the vanilla compiler? It adds CPS transformations (aka dataflow concurrency). await somePromiseProducingAsyncCall(), defer result
div result |
@stanch That snippet you've offered isn't syntatically-valid IcedCoffeeScript, nor is it set up to use promises correctly. This is what you'd use in ICS to hook up to a promise: await somePromiseProducingAsyncCall().then defer result
div result But, like all IcedCoffeeScript, it compiles to a horrible mess: var result, __iced_deferrals, __iced_k, __iced_k_noop,
_this = this;
__iced_k = __iced_k_noop = function() {};
(function(__iced_k) {
__iced_deferrals = new iced.Deferrals(__iced_k, {});
somePromiseProducingAsyncCall().then(__iced_deferrals.defer({
assign_fn: (function() {
return function() {
return result = arguments[0];
};
})(),
lineno: 0
}));
__iced_deferrals._fulfill();
})(function() {
return div(result);
}); And if you have more than one promise to handle, you need to do something like this: await
promiseForDiv.then defer divContent
promiseForSpan.then defer spanContent
div divContent
span spanContent This is hardly any better than doing it with promises alone, which would look like this: Q.spread [promiseForDiv, promiseForSpan], (divContent, spanContent) ->
div divContent
span spanContent And the ICS version has the added disadvantage of compiling to CPS-transformed mess instead of JavaScript almost exactly like the source CoffeeScript. Note that either method still requires that the actual templating process remains completely synchronous: All asynchronous calls are resolved to their results before the templating starts. Async support built into the template engine would allow for asynchronous calls to be resolved during a template's processing. It'd make the above example come out like this: div promiseForDiv
span promiseForSpan |
Still interest here? Unless I'm misunderstanding the solutions proposed above, anyone rendering a template with promises would have to wait until all promises had resolved before they'd get any HTML back, so it shouldn't matter from the caller's perspective whether Teacup renders blocks as promises resolve into space reserved earlier, or resolves all promises before starting rendering. If that's the case, we could write a small wrapper / mixin (teacup-as-promised?) that would change the render signature to always return a promise, and would resolve all promise arguments before starting rendering. Happy to throw together a proof of concept if there's interest. |
@hurrymaplelad did you ever make progress on this? |
@scien never heard more interest. Assuming ES6 promises, I'd I'd start with something like: Teacup::renderAsync = (template, args...) ->
Promise.all(args)
.then (resolvedArgs) =>
@render template, resolvedArgs... |
@hurrymaplelad threw together a proof of concept using what i found to be
there are still some issues to discuss that would likely come up in any implementation due to teacup using a single string buffer to render all templates. with any async implementation, you'd be able to have multiple renders taking place at once, and you may have nested renders, so the htmlOut implementation would likely need to change. |
Teacup doesn't have any support for asynchronous templating at present. There are various JS templating engines with async support; here's a few examples:
Could and should asynchronous support be added to Teacup, too? (For what it's worth, I'd personally most prefer promise support, as in QEJS, if any async support is to be added.) I expect adding such will complicate the currently-pretty-simple codebase rather a lot, though, so it's fine if it's left out.
The text was updated successfully, but these errors were encountered: