Enforce v11 resource guards in render and tags, fix for offset/else behavior, and update tutorials for Tag-class registration. Co-authored-by: Cursor <[email protected]>
4.4 KiB
title
| title |
|---|
| Sync and Async |
LiquidJS supports both synchronous and asynchronous evaluation, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async modes, LiquidJS tags are implemented as generators.
Sync and Async API
All major methods on Liquid support both sync and async. These methods return Promises:
render()renderFile()parseFile()parseAndRender()evalValue()
The synchronous version of methods contains a Sync suffix:
renderSync()renderFileSync()parseFileSync()parseAndRenderSync()evalValueSync()
Implement Sync-Compatible Tags
LiquidJS uses a generator-based async implementation to support both async and sync in one piece of tag implementation. For example, below UpperTag can be used in both engine.renderSync() and engine.render().
import { TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid } from 'liquidjs'
// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
private value: Value
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
this.value = new Value(token.args, liquid)
}
* render (ctx: Context, emitter: Emitter) {
const title = yield this.value.value(ctx)
emitter.write(title.toUpperCase())
}
})
All built-in tags are implemented this way and are safe to use in both sync and async modes (I'll call it sync-compatible). To make your custom tag sync-compatible, you'll need to:
- declare render function as
* render(), in which - do not directly
return <Promise>, and - do not call any APIs that return a Promise.
You can write output with emitter.write() or return / return yield an HTML string — both are emitted to output. Returning is handy for simple tags that produce one value (for example {% cycle %}); use emitter.write() when writing output incrementally or when delegating via yield renderTemplates(), since nested templates write through the shared emitter.
Call APIs that return a Promise
But LiquidJS is Promise-friendly, right? You can still call Promise-based functions and wait for that Promise within tag implementations. Just replace await with yield. e.g. we're calling fs.readFile() which returns a Promise:
* render (ctx: Context, emitter: Emitter) {
const file = yield this.value.value(ctx)
const title = yield fs.readFile(file, 'utf8')
emitter.write(title.toUpperCase())
}
Now that this * render() calls an API that returns a Promise, so it's no longer sync-compatible.
{% note info Non Sync-Compatible Tags %}
Non sync-compatible tags are also valid tags, will work just fine for asynchronous API calls. When called synchronously, tags that return a Promise will be rendered as [object Promise].
{% endnote %}
Convert LiquidJS async Generator to Promise
You can convert a Generator to Promise by toPromise, for example:
import { TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid, toPromise } from 'liquidjs'
// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
private value: Value
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
this.value = new Value(token.args, liquid)
}
async render (ctx: Context, emitter: Emitter) {
const title = await toPromise(this.value.value(ctx))
emitter.write(title.toUpperCase())
}
})
Async only Tags
If your tag is intended to be used only asynchronously, it can be declared as async render() so you can use await in its implementation directly:
import { toPromise, TagToken, Context, Emitter, TopLevelToken, Value, Tag, Liquid } from 'liquidjs'
// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', class UpperTag extends Tag {
private value: Value
constructor (token: TagToken, remainTokens: TopLevelToken[], liquid: Liquid) {
super(token, remainTokens, liquid)
this.value = new Value(token.args, liquid)
}
async render (ctx: Context, emitter: Emitter) {
const title = await toPromise(this.value.value(ctx))
emitter.write(`<h1>${title}</h1>`)
}
})