I'm making this silly function that will make a try/catch for any function and return it as a tuple with data and error
export type CatchReturn<T> = [data: T | undefined, error: unknown | undefined];
export const tcatch = <T>(
tryFunc: (() => T) | (() => Promise<T>)
) => {
try {
const res = tryFunc();
if (res instanceof Promise) {
return res
.then<CatchReturn<T>>((r) => [r, undefined])
.catch<CatchReturn<T>>((e) => [undefined, e]);
} else {
return [res, undefined] as CatchReturn<T>;
}
} catch (e) {
return [undefined, e] as CatchReturn<T>;
}
};
The function works as expected, but what I'm not able to do is correctly type the return type.
At the moment, the return type is CatchReturn<T> | Promise<CatchReturn<T>>
but I wanted the return type to only be CatchReturn<T>
if tryFunc: (() => T)
and Promise<CatchReturn<T>>
only if tryFunc: (() => Promise<T>)
.
So for example:
How it's working at the moment
const syncRes = tcatch(() => {}) // return type is CatchReturn<T> | Promise<CatchReturn<T>>
const asyncRes = tcatch(async () => {}) // return type is CatchReturn<T> | Promise<CatchReturn<T>>
How I expect it to work
const syncRes = tcatch(() => {}) // return type should be CatchReturn<T>
const asyncRes = tcatch(async () => {}) // return type should be Promise<CatchReturn<T>>
I tried using conditional types in the return type, but always got errors and I'm not 100% sure if this is even possible.
CodePudding user response:
This is a job for function overloads. You simply declare one function signature for promises, and another for other functions. Then your implementation returns a union of both:
// signatures
export function tcatch<T>(tryFunc: () => Promise<T>): Promise<CatchReturn<T>>
export function tcatch<T>(tryFunc: () => T): CatchReturn<T>
// implementation
export function tcatch<T>(
tryFunc: (() => T) | (() => Promise<T>)
): CatchReturn<T> | Promise<CatchReturn<T>> {
//...
}
Which seems to work as you expect:
const syncRes = tcatch(() => {}) // CatchReturn<void>
const asyncRes = tcatch(async () => {}) // Promise<CatchReturn<void>>
CodePudding user response:
Not so elegant way, but...
export type CatchReturn<T> = [data: T | undefined, error: unknown | undefined];
type MaybeMappedPromise<F extends () => unknown> = ReturnType<F> extends Promise<infer T> ? Promise<CatchReturn<T>> : CatchReturn<ReturnType<F>>
function tcatch<T extends unknown, F extends (() => T) | (() => Promise<T>)>(
tryFunc: F
): MaybeMappedPromise<F> {
try {
const res = tryFunc();
if (res instanceof Promise) {
return res
.then<CatchReturn<T>>((r) => [r, undefined])
.catch<CatchReturn<T>>((e) => [undefined, e]) as MaybeMappedPromise<F>;
} else {
return [res, undefined] as MaybeMappedPromise<F>;
}
} catch (e) {
return [undefined, e] as MaybeMappedPromise<F>;
}
}
const syncRes = tcatch(() => {}) // return type should be CatchReturn<T>
const asyncRes = tcatch(async () => {}) // return type should be Promise<CatchReturn<T>>