Sync and Async

LiquidJS supports both sync and async evaluate, and can be used with Promises. To reuse the same set of tag/filter implementations in both sync and async, LiquidJS tags are implemented as generators.

Sync and Async API

All major methods on Liquid supports 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

Requirements

All builtin tags are sync-compatible and safe to use for both sync and async APIs. To make your custom tag sync-compatible, you’ll need to avoid return a Promise. That means the render(context, emitter):

  • Should not directly return <Promise>, and
  • Should not be declared as async.
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].

Await Promises

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 and keep * render() instead of async render(). e.g.

import { TagToken, Context, Emitter, TopLevelToken } from 'liquidjs'

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
    parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
        this.str = tagToken.args
    },
    * render: function(ctx: Context) {
        // _evalValue will behave synchronously when called by synchronous API
        // in which case `ctx.sync == true`
        var str = yield this.liquid._evalValue(this.str, ctx)
        return str.toUpperCase()
    }
})

See this JSFiddle: http://jsfiddle.net/ctj364up/6/.

Async-only Tags

For tags that intended to be used only by async API, or those cannot be implemented synchronously, there’s no difference between using generator-base syntax or async syntax. I’ll call them async-only tags.

For example, if the above this.liquid._evalValue() doesn’t respect ctx.sync and always returns a Promise, even if the tag is implemented using * render() and yield this.liquid._evalValue(), it will be rendered as <object Promise> anyway.

For async-only tags, you can use async syntax at will. Be careful some APIs in LiquidJS return Promises and others return Generators. You’ll need toPromise API to convert a Generator to a Promise, for example:

import { TagToken, Context, Emitter, TopLevelToken, toPromise } from 'liquidjs'

// Usage: {% upper "alice" %}
// Output: ALICE
engine.registerTag('upper', {
    parse: function(tagToken: TagToken, remainTokens: TopLevelToken[]) {
        this.str = tagToken.args; // name
    },
    render: async function(ctx: Context) {
        var str = await toPromise(this.liquid._evalValue(this.str, ctx));
        // Or use the alternate API that returns a Promise
        // var str = await this.liquid.evalValue(this.str, ctx);
        return str.toUpperCase()
    }
});

See this JSFiddle: http://jsfiddle.net/ctj364up/5/.