Skip to content

Commit a07dd41

Browse files
authored
feat: auto unwrap QuansyncGenerator (#6)
1 parent feef8a2 commit a07dd41

File tree

2 files changed

+56
-25
lines changed

2 files changed

+56
-25
lines changed

src/index.ts

+29-25
Original file line numberDiff line numberDiff line change
@@ -4,15 +4,15 @@ export interface QuansyncInputObject<Return, Args extends any[]> {
44
async: (...args: Args) => Promise<Return>
55
}
66

7-
export type QuansyncInputGenerator<Return, Args extends any[]>
7+
export type QuansyncGeneratorFn<Return, Args extends any[]>
88
= ((...args: Args) => QuansyncGenerator<Return>)
99

1010
export type QuansyncInput<Return, Args extends any[]> =
1111
| QuansyncInputObject<Return, Args>
12-
| QuansyncInputGenerator<Return, Args>
12+
| QuansyncGeneratorFn<Return, Args>
1313

1414
export type QuansyncGenerator<Return = any, Yield = unknown> =
15-
Generator<Yield, Return, Awaited<Yield>>
15+
Generator<Yield, Return, Awaited<Yield>> & { __quansync?: true }
1616

1717
/**
1818
* "Superposition" function that can be consumed in both sync and async contexts.
@@ -32,6 +32,9 @@ function isThenable<T>(value: any): value is Promise<T> {
3232
function isGenerator<T>(value: any): value is Generator<T> {
3333
return value && typeof value === 'object' && typeof value[Symbol.iterator] === 'function'
3434
}
35+
function isQuansyncGenerator<T>(value: any): value is QuansyncGenerator<T> {
36+
return isGenerator(value) && '__quansync' in value
37+
}
3538

3639
const GET_IS_ASYNC = Symbol.for('quansync.getIsAsync')
3740

@@ -47,6 +50,7 @@ function fromObject<Return, Args extends any[]>(
4750
const fn = (...args: Args): any => {
4851
const iter = generator(...args) as unknown as QuansyncGenerator<Return, Args> & Promise<Return>
4952
iter.then = (...thenArgs) => options.async(...args).then(...thenArgs)
53+
iter.__quansync = true
5054
return iter
5155
}
5256
fn.sync = options.sync
@@ -68,36 +72,36 @@ function fromPromise<T>(promise: Promise<T> | T): QuansyncFn<T, []> {
6872
function unwrapYield(value: any, isAsync?: boolean): any {
6973
if (value === GET_IS_ASYNC)
7074
return isAsync
75+
if (isQuansyncGenerator(value))
76+
return isAsync ? iterateAsync(value) : iterateSync(value)
7177
if (!isAsync && isThenable(value))
7278
throw new Error(ERROR_PROMISE_IN_SYNC)
7379
return value
7480
}
7581

76-
function fromGenerator<Return, Args extends any[]>(
77-
generator: QuansyncInputGenerator<Return, Args>,
78-
): QuansyncFn<Return, Args> {
79-
function sync(...args: Args): Return {
80-
const iterator = generator(...args)
81-
let current = iterator.next()
82-
while (!current.done) {
83-
current = iterator.next(unwrapYield(current.value))
84-
}
85-
return unwrapYield(current.value)
82+
function iterateSync<Return>(generator: QuansyncGenerator<Return, unknown>): Return {
83+
let current = generator.next()
84+
while (!current.done) {
85+
current = generator.next(unwrapYield(current.value))
8686
}
87+
return unwrapYield(current.value)
88+
}
8789

88-
async function async(...args: Args): Promise<Return> {
89-
const iterator = generator(...args)
90-
let current = iterator.next()
91-
while (!current.done) {
92-
current = iterator.next(await unwrapYield(current.value, true))
93-
}
94-
return current.value
90+
async function iterateAsync<Return>(generator: QuansyncGenerator<Return, unknown>): Promise<Return> {
91+
let current = generator.next()
92+
while (!current.done) {
93+
current = generator.next(await unwrapYield(current.value, true))
9594
}
95+
return current.value
96+
}
9697

98+
function fromGeneratorFn<Return, Args extends any[]>(
99+
generatorFn: QuansyncGeneratorFn<Return, Args>,
100+
): QuansyncFn<Return, Args> {
97101
return fromObject({
98-
name: generator.name,
99-
async,
100-
sync,
102+
name: generatorFn.name,
103+
async: (...args) => iterateAsync(generatorFn(...args)),
104+
sync: (...args) => iterateSync(generatorFn(...args)),
101105
})
102106
}
103107

@@ -110,7 +114,7 @@ export function quansync<Return, Args extends any[] = []>(
110114
if (isThenable(options))
111115
return fromPromise<Return>(options)
112116
if (typeof options === 'function')
113-
return fromGenerator(options)
117+
return fromGeneratorFn(options)
114118
else
115119
return fromObject(options)
116120
}
@@ -134,5 +138,5 @@ export function quansyncMacro<Return, Args extends any[] = []>(
134138
export function toGenerator<T>(promise: Promise<T> | QuansyncGenerator<T> | T): QuansyncGenerator<T> {
135139
if (isGenerator(promise))
136140
return promise
137-
return fromPromise(promise as Promise<T>)()
141+
return fromPromise(promise)()
138142
}

test/index.test.ts

+27
Original file line numberDiff line numberDiff line change
@@ -132,3 +132,30 @@ it('handle errors', async () => {
132132
await expect(throwError.async()).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: async error]`)
133133
expect(() => throwError.sync()).toThrowErrorMatchingInlineSnapshot(`[Error: sync error]`)
134134
})
135+
136+
it('yield generator', async () => {
137+
const toString = quansync({
138+
name: 'toString',
139+
sync: (value: any) => String(value),
140+
async: async (value: any) => String(value),
141+
})
142+
143+
function* produce() {
144+
yield 1
145+
yield 2
146+
return 3
147+
}
148+
149+
const multiply = quansync(function* () {
150+
const plainGenerator = produce()
151+
expect(yield plainGenerator).toBe(plainGenerator)
152+
153+
const result = (yield toString('str')) as string
154+
expect(result).toBe('str')
155+
156+
return result + (yield * toString('str'))
157+
})
158+
159+
expect(multiply.sync()).toBe('strstr')
160+
await expect(multiply.async()).resolves.toBe('strstr')
161+
})

0 commit comments

Comments
 (0)