Why does TypeScript have both `void` and `undefined`?
Solution 1:
void
has special meaning in function return types, and is not an alias for undefined
. Thinking of it this way is very wrong. Why?
The intent of void
is that a function's return value will not be observed. This is very different from will be undefined
. It's important to have this distinction so that you can properly describe functions like forEach
. Let's consider a freestanding version of Array#forEach
, written with undefined
instead of void
in the callback return position:
declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
If you tried to use this function:
let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));
You'd get an error:
Type "number" is not assignable to type "undefined"
This is a correct error - you said you wanted a function that returned the value undefined
, but you actually provided a function that returned the value number
because that's what Array#push
returns!
Using void
instead means that forEach
promises not to use the return value, so it can be called with a callback that returns any value
declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
// OK
forEach([1, 2, 3], el => target.push(el));
Why not just use any
? If you're actually the one implementing forEach
, you really don't want that - having an any
floating is a dangerous thing that can defeat typechecking very easily.
The corollary to this is that if you have some function expression whose return type is void
, you cannot say with any certainty that the result of invoking that function is undefined
.
Again, void
is not an alias for undefined
and an expression of type void
may have any value, not just undefined
In a function body whose return type is explicitly listed as void
, TypeScript will stop you from "accidently" returning a value, even though this wouldn't create a type system violation. This is helpful for catching bugs that appear from a refactoring:
// Old version
function fn(arr: number[]): void {
const arr1 = arr.map(x => {
return 3;
});
}
// New version
function fn(arr: number[]): void {
for (const x of arr) {
// Oops, meant to do something else
return 3;
};
}