同步和异步

LiquidJS 支持同步调用也支持异步调用,支持 Promise。为了同异步复用一套标签和过滤器,LiquidJS 标签用生成器来实现。

同异步 API

Liquid 上主要的方法都支持同步和异步,下面这些方法返回 Promise

  • render()
  • renderFile()
  • parseFile()
  • parseAndRender()
  • evalValue()

它们的同步版本带一个 Sync 后缀:

  • renderSync()
  • renderFileSync()
  • parseFileSync()
  • parseAndRenderSync()
  • evalValueSync()

如何实现兼容同步的标签

LiquidJS 使用基于生成器的异步实现,来让同一份代码支持同步和异步调用。例如下面的 UpperTag 既可以用于 engine.renderSync() 也可以用于 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())
  }
})

所有内置标签都兼容同步,可以安全地用于同步或异步 API。实现同时支持同异步的标签,需要:

  • render 函数声明成 * render(),并且在里面
  • 不能直接 return <Promise>
  • 不能调用会返回 Promise 的函数。

调用返回 Promise 的函数

但 LiquidJS 是支持 Promise 的,你仍然可以调用返回 Promise 的方法并等它 resolve。只需要把 await 换成 yield。例如:

* render (ctx: Context, emitter: Emitter) {
  const file = yield this.value.value(ctx)
  const title = yield fs.readFile(file, 'utf8')
  emitter.write(title.toUpperCase())
}

现在 * render() 调用了一个返回 Promise 的 API,它就不再兼容同步了。不兼容同步的标签也仍然是合法标签,在异步 API 下也会正常运行。被同步调用时,返回 Promise 的标签会被渲染成 [object Promise]

把 LiquidJS 生成器转换成 Promise

有些 LiquidJS API 会返回 Promise,有些会返回生成器。你可以用 toPromise 来把生成器转换为 Promise,比如:

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 render(),这样就可以使用更熟悉的 await 了:

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>`)
  }
})