void
, when used in function return types, holds a specific significance; it is not interchangeable with undefined
. Misinterpreting it in this manner can lead to serious misconceptions. So why is this understanding incorrect?
The purpose of declaring a function's return type as void
is to indicate that the returned value should not be inspected or utilized. This differs significantly from stating that it will simply be undefined
. This distinction is crucial for accurately defining functions like forEach
. Now, consider an alternative version of Array#forEach
, where we use undefined
instead of void
in the callback:
declare function forEach<T>(arr: T[], callback: (el: T) => undefined): void;
If you attempt to apply this modified function:
let target: number[] = [];
forEach([1, 2, 3], el => target.push(el));
You will encounter an error message:
Type "number" is not assignable to type "undefined"
This error is appropriate - you declared that the function should return undefined
, yet you actually provided one returning number
because Array#push
returns a value!
By using void
instead, forEach
guarantees that the return value is irrelevant, allowing you to utilize callbacks that may return any value
declare function forEach<T>(arr: T[], callback: (el: T) => void): void;
let target: number[] = [];
// Works fine
forEach([1, 2, 3], el => target.push(el));
Why not opt for any
? If you are implementing forEach
, it is strongly advised against - having an untyped any
floating around can easily undermine type safety.
A crucial point to note is that if a function expression has a return type of void
, it does not mean that invoking that function will always yield undefined
.
In essence, void
should not be seen as synonymous with undefined
; a type of void
can potentially hold any value, not limited to just undefined
Within a function body where the return type is explicitly defined as void
, TypeScript will prevent accidental return statements, even though such action would not violate the type system. This feature aids in catching bugs that may arise during code modifications:
// Previous implementation
function fn(arr: number[]): void {
const arr1 = arr.map(x => {
return 3;
});
}
// Updated version
function fn(arr: number[]): void {
for (const x of arr) {
// Mistakenly returning a value here
return 3;
};
}