eta-dev / eta

Embedded JS template engine for Node, Deno, and the browser. Lighweight, fast, and pluggable. Written in TypeScript
https://eta.js.org
MIT License
1.43k stars 65 forks source link

Globally async data object and global await feature #99

Open SilentVoid13 opened 3 years ago

SilentVoid13 commented 3 years ago

Is your feature request related to a problem? Please describe.

No, not really.

Describe the solution you'd like

I'd like to be able to await for all async functions in my data object without having to add the "await" keyword in front of each of them (maybe an option in the config). In my case, I don't want users to ask themselves "is this function async or not?".

But the biggest benefit of this, and this is the reason of my feature request, is that I would like eta to be able to await for all the Promises of async functions globally (using something like Promise.all()) instead of individually, to speed things up. Right now, eta is awaiting each async function one by one. This means that if an async function takes 1 second, another 2 seconds and another 3 seconds, eta will take a total of 6 seconds instead of 3 (maximum possible time among the 3 functions) to render the templates.

Describe alternatives you've considered

Right now I'm using a kinda ugly hack to be able to await all for async functions in my data object: I'm basically adding the "await" keyword in front of every template variable. It works fine but as I said before, since eta awaits each function one by one, this can be very slow.

let async_content = content.replace(new RegExp("it\.", "g"), "await it\.");
content = await Eta.renderAsync(async_content, context);

Additional context

Ideally, I'd like to be able to have something like this: multiple async functions (d1, d2, d3) in my data object and have eta do something like Promise.all in the background to speed things up.

function delay(ms: number) {
    return new Promise( resolve => setTimeout(resolve, ms) );
};

let d1 = delay(1000);
let d2 = delay(2000);
let d3 = delay(3000)

Promise.all([d1, d2, d3]).then((values) => {
    console.log("FINISHED !");
});

Among the popular templating js engines I tested (nunjucks, ejs, handlebars, ...), eta is probably the best templating engine to deal with async functions in the data object (kudos for that!). Adding this feature would really make async functions a breeze to use.

Thanks for this excellent tool.

shadowtime2000 commented 3 years ago

@SilentVoid13 So what you are requesting is to have Eta crawl the templates and find async functions to and automatically add them to a list? That seems kind of heavy and stuff and a little out of scope...

SilentVoid13 commented 3 years ago

@shadowtime2000 You're right maybe the global Promise.all() is a bit out of scope for this tool. I wouldn't mind implementing something like that myself but AFAIK there is no way to modify the way Eta deals with functions. I'd really like to be able to implement something like that while still using Eta though, do you have any suggestions on how I could achieve something like that ?

The option to await for all async functions by default seems like a valid use case though, and I don't think I'll be the only one using such a feature.

SilentVoid13 commented 3 years ago

Hey @shadowtime2000 any updates on this ?

I modified some stuff in eta to get it working on my side: added an alwaysAwait optional boolean option in the config and added the following code in the function compileScope in compile-string.ts:

[...]
if (type === 'r') {
  // raw

  if (config.alwaysAwait) {
    content = 'await ' + content;
  }

  if (config.filter) {
    content = 'E.filter(' + content + ')'
  }

  returnStr += 'tR+=' + content + '\n'
} else if (type === 'i') {
  // interpolate

  if (config.alwaysAwait) {
    content = 'await ' + content;
  }

  if (config.filter) {
    content = 'E.filter(' + content + ')'
  }

  if (config.autoEscape) {
    content = 'E.e(' + content + ')'
  }
  returnStr += 'tR+=' + content + '\n'
  // reference
}
[...]

Since await does nothing on variables / synchronous functions this seems like an "ok" solution.

Do you want me to make a PR for this or do you disagree with the idea of this option ? As I said I think this can be a valuable option for other users besides me.