6.7 KiB
Async Taglib
Marko includes a taglib that supports the more efficient and simpler "Pull Model "approach to providing templates with view model data.
- Push Model: Request all needed data upfront and wait for all of the data to be received before building the view model and then rendering the template.
- Pull Model: Pass asynchronous data provider functions to template immediately start rendering the template. Let the template pull the data needed during rendering.
The Pull Model approach to template rendering requires the use of a templating engine that supports asynchronous template rendering (e.g. marko and dust). This is because before rendering the template begins not all of data may have been fully retrieved. Parts of a template that depend on data that is not yet available are rendered asynchronously with the Pull Model approach.
Push Model versus Pull Model
The problem with the traditional Push Model approach is that template rendering is delayed until all data has been fully received. This reduces the time to first byte, and it also may result in the server sitting idle while waiting for data to be loaded from remote services. In addition, if certain data is no longer needed by a template then only the template needs to be modified and not the controller.
With the new Pull Model approach, template rendering begins immediately. In addition, sections of the template that depend on data from data providers are rendered asynchronously and await only the associated data provider's completion. The template rendering will only be delayed for data that the template actually needs.
Example
var template = require('./template.marko');
module.exports = function(req, res) {
var userId = req.query.userId;
template.render({
userProfileDataProvider: function(callback) {
userProfileService.getUserProfile(userId, callback);
}
}, res);
}
<await(userProfile from data.userProfileDataProvider)>
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</await>
Out-of-order Flushing
The marko-async taglib also supports out-of-order flushing. Enabling out-of-order flushing requires two steps:
- Add the
client-reorderattribute to the<await>tag:
<await(userProfile from data.userProfileDataProvider) client-reorder=true>
<ul>
<li>
First name: ${userProfile.firstName}
</li>
<li>
Last name: ${userProfile.lastName}
</li>
<li>
Email address: ${userProfile.email}
</li>
</ul>
</await>
- Add the
<await-reorderer>to the end of the page.
If client-reorder is true then a placeholder element will be rendered to the output instead of the final HTML for the await instance. The instance will be instead rendered at the end of the page and client-side JavaScript code will be used to move the await's contents into the proper place in the DOM. The <await-reorderer> will be where the out-of-order instances are rendered before they are moved into place. If there are any out-of-order instances then inline JavaScript code will be injected into the page at this location to move the DOM nodes into the proper place in the DOM.
Taglib API
<await>
Required Argument:
<await(varName from data.provider)>
var: Variable name to use when consuming the data provided by the data providerdata provider: The source of data to await. Must be a reference to one of the following:Function(callback)Function(args, callback)Promise- Data
Supported Attributes:
arg(expression): The argument object to provide to the data provider function.arg-<arg_name>(string): An argument to add to theargobject provided to the data provider function.client-reorder(boolean): Iftrue, then the await instances will be flushed in the order they complete and JavaScript running on the client will be used to move the await instances into the proper HTML order in the DOM. Defaults tofalse.error-message(string): Message to output if the data provider errors out. Specifying this will prevent the rendering from aborting.name(string): Name to assign to this await instance. Used for debugging purposes as well as by theshow-afterattribute (see below).placeholder(string): Placeholder text to show while waiting for a data provider to complete. Only applicable ifclient-reorderis set totrue.show-after(string): Whenclient-reorderis set totruethen displaying this instance's content will be delayed until the referenced await instance is shown.timeout(integer): Override the default timeout of 10 seconds with this param. Units are inmilliseconds sotimeout="40000"would give a 40 second timeout.timeout-message(string): Message to output if the data provider times out. Specifying this will prevent the rendering from aborting.
<await-placeholder>
This tag can be used to control what text is shown while an out-of-order await instance is waiting to be loaded. Only applicable if client-reorder is set to true.
Example:
<await(user from data.userDataProvider) client-reorder>
<await-placeholder>
Loading user data...
</await-placeholder>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</await>
<await-error>
This tag can be used to control what text is shown when a data provider errors out.
Example:
<await(user from data.userDataProvider)>
<await-error>
An error occurred!
</await-error>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</await>
<await-timeout>
This tag can be used to control what text is shown when a data provider times out.
Example:
<await(user from data.userDataProvider)>
<await-timeout>
A timeout occurred!
</await-timeout>
<ul>
<li>First name: ${user.firstName}</li>
<li>Last name: ${user.lastName}</li>
</ul>
</await>
<await-reorderer>
Container for all out-of-order await instances. If any <await> tags have client-reorder set to true then this tag needs to be included in the page template (typically, right before the closing </body> tag).
Example:
<!DOCTYPE html>
<html>
...
<body>
...
<await-reorderer/>
</body>
</html>