feat: export toPromise and toValue, see #158

This commit is contained in:
harttle
2020-08-05 00:46:01 +08:00
parent 0dbb7792d7
commit 2e5ab98bfc
5 changed files with 33 additions and 17 deletions
+5 -5
View File
@@ -14,7 +14,7 @@ import { FilterMap } from './template/filter/filter-map'
import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options' import { LiquidOptions, normalizeStringArray, NormalizedFullOptions, applyDefault, normalize } from './liquid-options'
import { FilterImplOptions } from './template/filter/filter-impl-options' import { FilterImplOptions } from './template/filter/filter-impl-options'
import { FS } from './fs/fs' import { FS } from './fs/fs'
import { toThenable, toValue } from './util/async' import { toPromise, toValue } from './util/async'
export * from './types' export * from './types'
@@ -49,7 +49,7 @@ export class Liquid {
return this.renderer.renderTemplates(tpl, ctx) return this.renderer.renderTemplates(tpl, ctx)
} }
public async render (tpl: Template[], scope?: object, opts?: LiquidOptions): Promise<string> { public async render (tpl: Template[], scope?: object, opts?: LiquidOptions): Promise<string> {
return toThenable(this._render(tpl, scope, opts, false)) return toPromise(this._render(tpl, scope, opts, false))
} }
public renderSync (tpl: Template[], scope?: object, opts?: LiquidOptions): string { public renderSync (tpl: Template[], scope?: object, opts?: LiquidOptions): string {
return toValue(this._render(tpl, scope, opts, true)) return toValue(this._render(tpl, scope, opts, true))
@@ -60,7 +60,7 @@ export class Liquid {
return this._render(tpl, scope, opts, sync) return this._render(tpl, scope, opts, sync)
} }
public async parseAndRender (html: string, scope?: object, opts?: LiquidOptions): Promise<string> { public async parseAndRender (html: string, scope?: object, opts?: LiquidOptions): Promise<string> {
return toThenable(this._parseAndRender(html, scope, opts, false)) return toPromise(this._parseAndRender(html, scope, opts, false))
} }
public parseAndRenderSync (html: string, scope?: object, opts?: LiquidOptions): string { public parseAndRenderSync (html: string, scope?: object, opts?: LiquidOptions): string {
return toValue(this._parseAndRender(html, scope, opts, true)) return toValue(this._parseAndRender(html, scope, opts, true))
@@ -88,7 +88,7 @@ export class Liquid {
throw this.lookupError(file, options.root) throw this.lookupError(file, options.root)
} }
public async parseFile (file: string, opts?: LiquidOptions): Promise<Template[]> { public async parseFile (file: string, opts?: LiquidOptions): Promise<Template[]> {
return toThenable(this._parseFile(file, opts, false)) return toPromise(this._parseFile(file, opts, false))
} }
public parseFileSync (file: string, opts?: LiquidOptions): Template[] { public parseFileSync (file: string, opts?: LiquidOptions): Template[] {
return toValue(this._parseFile(file, opts, true)) return toValue(this._parseFile(file, opts, true))
@@ -108,7 +108,7 @@ export class Liquid {
return value.value(ctx) return value.value(ctx)
} }
public async evalValue (str: string, ctx: Context): Promise<any> { public async evalValue (str: string, ctx: Context): Promise<any> {
return toThenable(this._evalValue(str, ctx)) return toPromise(this._evalValue(str, ctx))
} }
public evalValueSync (str: string, ctx: Context): any { public evalValueSync (str: string, ctx: Context): any {
return toValue(this._evalValue(str, ctx)) return toValue(this._evalValue(str, ctx))
+1 -1
View File
@@ -21,7 +21,7 @@ export class Hash {
* render (ctx: Context) { * render (ctx: Context) {
const hash = {} const hash = {}
for (const key of Object.keys(this.hash)) { for (const key of Object.keys(this.hash)) {
hash[key] = evalToken(this.hash[key], ctx) hash[key] = yield evalToken(this.hash[key], ctx)
} }
return hash return hash
} }
+1
View File
@@ -17,3 +17,4 @@ export { TopLevelToken } from './tokens/toplevel-token'
export { Tokenizer } from './parser/tokenizer' export { Tokenizer } from './parser/tokenizer'
export { Hash } from './template/tag/hash' export { Hash } from './template/tag/hash'
export { evalToken, evalQuotedToken } from './render/expression' export { evalToken, evalQuotedToken } from './render/expression'
export { toPromise, toThenable, toValue } from './util/async'
+16 -10
View File
@@ -7,7 +7,7 @@ interface Thenable {
catch (reject: resolver): Thenable; catch (reject: resolver): Thenable;
} }
function mkResolve (value: any) { function createResolvedThenable (value: any): Thenable {
const ret = { const ret = {
then: (resolve: resolver) => resolve(value), then: (resolve: resolver) => resolve(value),
catch: () => ret catch: () => ret
@@ -15,7 +15,7 @@ function mkResolve (value: any) {
return ret return ret
} }
function mkReject (err: Error) { function createRejectedThenable (err: Error): Thenable {
const ret = { const ret = {
then: (resolve: resolver, reject?: resolver) => { then: (resolve: resolver, reject?: resolver) => {
if (reject) return reject(err) if (reject) return reject(err)
@@ -30,43 +30,49 @@ function isThenable (val: any): val is Thenable {
return val && isFunction(val.then) return val && isFunction(val.then)
} }
function isCustomIterable (val: any): val is IterableIterator<any> { function isAsyncIterator (val: any): val is IterableIterator<any> {
return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return) return val && isFunction(val.next) && isFunction(val.throw) && isFunction(val.return)
} }
// convert an async iterator to a thenable (Promise compatible)
export function toThenable (val: IterableIterator<any> | Thenable | any): Thenable { export function toThenable (val: IterableIterator<any> | Thenable | any): Thenable {
if (isThenable(val)) return val if (isThenable(val)) return val
if (isCustomIterable(val)) return reduce() if (isAsyncIterator(val)) return reduce()
return mkResolve(val) return createResolvedThenable(val)
function reduce (prev?: any): Thenable { function reduce (prev?: any): Thenable {
let state let state
try { try {
state = (val as IterableIterator<any>).next(prev) state = (val as IterableIterator<any>).next(prev)
} catch (err) { } catch (err) {
return mkReject(err) return createRejectedThenable(err)
} }
if (state.done) return mkResolve(state.value) if (state.done) return createResolvedThenable(state.value)
return toThenable(state.value!).then(reduce, err => { return toThenable(state.value!).then(reduce, err => {
let state let state
try { try {
state = (val as IterableIterator<any>).throw!(err) state = (val as IterableIterator<any>).throw!(err)
} catch (e) { } catch (e) {
return mkReject(e) return createRejectedThenable(e)
} }
if (state.done) return mkResolve(state.value) if (state.done) return createResolvedThenable(state.value)
return reduce(state.value) return reduce(state.value)
}) })
} }
} }
export function toPromise (val: IterableIterator<any> | Thenable | any): Promise<any> {
return Promise.resolve(toThenable(val))
}
// get the value of async iterator in synchronous manner
export function toValue (val: IterableIterator<any> | Thenable | any) { export function toValue (val: IterableIterator<any> | Thenable | any) {
let ret: any let ret: any
toThenable(val) toThenable(val)
.then((x: any) => { .then((x: any) => {
ret = x ret = x
return mkResolve(ret) return createResolvedThenable(ret)
}) })
.catch((err: Error) => { .catch((err: Error) => {
throw err throw err
+10 -1
View File
@@ -1,10 +1,19 @@
import { toThenable, toValue } from '../../../src/util/async' import { toThenable, toPromise, toValue } from '../../../src/util/async'
import { expect, use } from 'chai' import { expect, use } from 'chai'
import * as chaiAsPromised from 'chai-as-promised' import * as chaiAsPromised from 'chai-as-promised'
use(chaiAsPromised) use(chaiAsPromised)
describe('utils/async', () => { describe('utils/async', () => {
describe('#toPromise()', function () {
it('should return a promise', async () => {
function * foo () {
return 'foo'
}
const result = await toPromise(foo())
expect(result).to.equal('foo')
})
})
describe('#toThenable()', function () { describe('#toThenable()', function () {
it('should support iterable with single return statement', async () => { it('should support iterable with single return statement', async () => {
function * foo () { function * foo () {