Since TypeScript is a superset of Javascript and 'Type' would be removed in the resulting js files, so I guess playing with 'type' would not work like this, right? Also, is there any better way to do it other than having two different methods?
type VoidToVoidFunc = () => void;
type VoidToPromiseVoidFunc = () => Promise<void>;
async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
if (typeof func == typeof VoidToVoidFunc)
func();
else
await func();
}
add(() => console.log(1));
As you can see here, when a non-thenable value follows await, an already-fulfilled Promise is constructed and used.
So the simplest implementation is to await the result of the function call. Indeed, await wraps a value in a Promise if it's not already a Promise:
type VoidToVoidFunc = () => void;
type VoidToPromiseVoidFunc = () => Promise<void>;
async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
await func();
// Do something else
}
add(() => console.log(1));
This behavior is correctly typed by TypeScript, indeed when you write const test = await func(); and hover the variable, its type is correctly inferred to void.
I guess the internal implementation of the type returned by await must look like something like this: type Await<T> = T extends Promise<infer R> ? R : T
TypeScript playground
The cleanest and most maintainable solution is the most strait forward
type VoidToVoidFunc = () => void;
type VoidToPromiseVoidFunc = () => Promise<void>;
async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
await func();
}
add(() => console.log("sync function", 1));
add(async () => {
await delay(1000);
console.log("async function", 2);
});
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Playground Link
async function add(func) {
await func();
}
add(() => console.log("sync function", 1));
add(async () => {
await delay(1000);
console.log("async function", 2);
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
As you can see from the above, behavior, applying await to a non Promise value simply wraps and unwraps it. As your add function by virtue of being denoted async is necessarily going to return a Promise and run asynchronously (scheduled on the next tick), more complex solutions offer no benefit in your case.
Check out the async function and Promise docs at MDN for further elaboration.
Thus, while there are indeed other ways to determine if a you received a callback that may or may not return a Promise, such as
type VoidToVoidFunc = () => void;
type VoidToPromiseVoidFunc = () => Promise<void>;
async function add(func: VoidToVoidFunc | VoidToPromiseVoidFunc) {
const couldBeAPromise = func();
if (couldBeAPromise && 'then' in couldBeAPromise) {
await couldBeAPromise;
}
}
add(() => console.log("sync function", 1));
add(async () => {
await delay(1000);
console.log("async function", 2);
});
function delay(ms: number) {
return new Promise(resolve => setTimeout(resolve, ms));
}
Playground Link
async function add(func) {
const couldBeAPromise = func();
if (couldBeAPromise && 'then' in couldBeAPromise) {
await couldBeAPromise;
}
}
add(() => console.log("sync function", 1));
add(async () => {
await delay(1000);
console.log("async function", 2);
});
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
keeping it simple is the way to go here.
Note: void is a TypeScript type expressing the contract of a function and guiding type inference. However, every JavaScript function and thus every TypeScript function actually returns a value. In this case func param of add returns either undefined or a Promise that resolves to undefined.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With