Typescript async function return type void vs Promise<void>
Yes there's a difference
void is a little like the opposite of any: the absence of having any type at all. You may commonly see this as the return type of functions that do not return a value
I guess Promise<void>
doesn't require explanation.
Now why the assignment in the question is allowed? Because target function (() => void
) can be called in all (*almost) the same situations as the source function. Have a look at simplified example:
declare let voidFunc: () => void;
declare let promiseFunc: () => Promise<void>;
voidFunc = promiseFunc; // OK
promiseFunc = voidFunc; // Error: Type 'void' is not assignable to type 'Promise<void>'
When you declare a variable with () => void
type, you are basically saying that it could be any function. So its value (function) could return anything. Take a look at this playground. I put there a few more function implementations to you to see that declaring a variable that receives a function is different than declaring a function itself.
Declaring the function itself has the behavior you expected.
const asyncFunc: () => void = async () => {
await new Promise(resolve => resolve());
};
const asyncFunc2: () => Promise<void> = async () => {
await new Promise(resolve => resolve());
};
const asyncFunc3 = async () => {
await new Promise(resolve => resolve());
};
// TS compiler complains about it
async function asyncFunc4(): void {
await new Promise(resolve => resolve());
}
async function asyncFunc4(): Promise<void> {
await new Promise(resolve => resolve());
}
Edit: Found this little explanation. It could help you understand it too. It says:
Do use the return type void for callbacks whose value will be ignored. Why: Using void is safer because it prevents you from accidentally using the return value of x in an unchecked way
This will work as well (notice that this is not even an async function):
const asyncFunc: () => void = () => {
return Math.random() > 0.5 ? 'hello' : 'world';
};
// OR this:
const asyncFunc2: () => void = () => {
return { nice: 'very '};
};
why?? Basically, you can have typescript interpret the types itself, or you can set a specific type to a constant.
Here, you defined a constant called asyncFunc
, which is of type () => void
. This means that typescript will disregard the return value of the function when defining the type. Think of it as if the user don't expect a value, so returning a value should still work fine and not break the code.
So, what's the type of asyncFunc
??
If you define the type of the constant yourself, the type will be () => void
, so the user won't expect any return value:
But if you let typescript interpret this itself, it should know the correct value.
So, for the function defined above (without the async), it will be () => string
:
So what should you do?
specifically for the case of defining a function as () => void
, the function won't check the return value at all and you can return anything (even a promise).
Defining a function as async will just wrap the return value in a promise, which the () => void
type will just ignore.
If you want to define a different type of the return value than the interpreted type, you can define an async function to return a specific promise with a nested type.
Something like this for your example:
const asyncFunc: () => Promise<void> = async () => {
await new Promise(resolve => resolve());
};
The thing is, that this will happen automatically without the type definition:
So it's pretty redundant. you can just let typescript interpret the types unless you want to change the return type to something else.
So, in the example that might return hello
or world
we might want to let the user know that the return value can be any string instead of these two values.
Then, it makes sense to change the definition of the return value to something else:
You can combine any of these to achieve a workflow you like, but be aware that () => void
is kind of a special case with this type system (since any other return value besides void
will throw a type error.