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