Borislav Hadzhiev
Thu Mar 03 2022·3 min read
Photo by Nathan Dumlao
Use a union type to define a function with multiple return types in
TypeScript, e.g. function getValue(num: number): string | number {}
. The
function must return a value that is represented in the union type, otherwise
the type checker throws an error.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'hello'; } // 👇️ const result1: string | number const result = getValue(4); if (typeof result === 'string') { // 👇️ result is a string here console.log(result.toUpperCase()); } else { // 👇️ result is a number here console.log(result.toFixed(2)); }
We used a union type to set the return type of a function with multiple return types.
You can use the same approach when working with arrow functions.
const getValue = (num: number): string | number => { if (num > 5) { return 100; } return 'hello'; };
You can include as many types as necessary in your union. Simply separate the
types with a pipe |
.
const getValue = (num: number): string | number | boolean => { if (num === 0) { return true; } if (num > 5) { return 100; } return 'hello'; };
The function in the example above returns a string
or number
.
The same approach can be used with type aliases, interfaces or classes.
class Animal { run() { console.log('the animal runs'); } } class Human { walk() { console.log('the human walks'); } } function getValue(num: number): Animal | Human { if (num > 5) { const animal = new Animal(); return animal; } const human = new Human(); return human; }
Here is an example of a function that returns a string array or a number array.
function getValue(num: number): string[] | number[] { if (num > 5) { return [100]; } return ['hello']; }
Because the functions in the examples have multiple return types, we have to use a type guard before accessing type-specific methods or properties.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'hello'; } // 👇️ const result1: string | number const result = getValue(4); if (typeof result === 'string') { // 👇️ result is a string here console.log(result.toUpperCase()); } else { // 👇️ result is a number here console.log(result.toFixed(2)); }
The result
variable stores a string
or number
, so TypeScript won't allow
us to access a string-specific built-in method like toUpperCase
until we can
narrow the type down.
The type of
the value stored in result
is a string in the if
block.
2
possible types, TypeScript knows that the type of the value is a number
in the else
block.How you narrow the type down might be different depending on the type of the values the function returns.
For example, if you had to check for an array, you would use
Array.isArray(myArray)
and if you had to check if a value is an instance of a
specific class you would use myInstance instanceof myClass
.
A quick and dirty way to get around having to check the type is to use a type assertion.
function getValue(num: number): string | number { if (num > 5) { return 100; } return 'hello'; } // 👇️ const result1: string const result = getValue(4) as string; // 👈️ type assertion console.log(result.toUpperCase()); // 👉️ "HELLO"
The as
keyword is a type assertion and sets the type of the result
variable
to a string
, without us having to use a type guard.
This approach should generally be avoided as it isn't type safe and would cause
a runtime error if the result
variable stored a number
.