WHAT ARE FUNCTIONS?
Functions are the fundamental building blocks of any application in JavaScript. They’re how you build up layers of abstraction, mimicking classes, information hiding, and modules.
In TypeScript, while there are classes, namespaces, and modules, functions still play a key role in describing how to do things.
WHAT TYPESCRIPT ADDS?
Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the types of both the input and output values of functions.
TypeScript also adds some new capabilities to the standard JavaScript functions to make them easier to work with.
SYNTAX AND STRUCTURE:
In TypeScript, function definition consists of a function name, its parameters with types, and its return type (which can be inferred by TypeScript).
DECLARE AN ARROW FUNCTION:
Following is the syntax to declare a function in TypeScript:
const functionName = (param1: string): string => {
// ...
return param1;
};
// functionName = Name of the function
// param1 = Name of the parameter (optional)
// (...): string = Return type of the function
In the above code, the syntax (param1: string): string =>
means a function with one parameter, named param1
, of type string
, that has a return value of type string
.
If you don't specify the return type, TypeScript automatically infers the return type for you as shown below:
Here, const functionName: (param1: string) => string
means, a function with a name functionName
with one parameter named param1
of type string
that has a return value of type string
.
DECLARE A FUNCTION WITH function
KEYWORD:
Declaring a function with the function
keyword is almost similar to an arrow function.
Take a look at the following code:
function functionName(param1: string): string {
// ...
return param1;
}
// functionName = Name of the function
// param1 = Name of the parameter (optional)
// (...): string = Return type of the function
TYPE ANNOTATIONS:
When you declare a function, you can add type annotations after each parameter to declare what type of parameters the function accepts. You can add return type annotations to the function as well.
Parameter type annotations go after the parameter name. Return type annotations appear after the parameter list.
PARAMETER TYPE ANNOTATIONS:
Take a look at the following code:
const add = (a: number, b: number) => {
return a + b;
};
console.log(add(1, 2));
In the above code, we declare an add
function that accepts parameters a
and b
that is of type number
.
So, if you try to break the rule and pass string
instead of the number
, you will get a compilation error as follows:
Also, if you try to add parameters more than what a function is accepting, you will get a compilation error, unlike JavaScript.
RETURN TYPE ANNOTATIONS:
Although we usually don’t need a return type annotation because TypeScript will infer the function’s return type based on its return statement. In some cases, you have to explicitly specify a return type for documentation purposes to prevent accidental changes or just for personal preference.
const returnANumber = (): number => {
return 1;
};
Here, we are specifying that the returnANumber
will return a value with a type
number
.
Now, if you specify the return type but don't return a value with valid type
, TypeScript will throw a compilation error as follows:
IDE SUPPORT FOR RETURN TYPE:
The advantage of knowing the return type of a function is you get great IDE support if you want to do more operations on the value that is being returned.
For example,
let parseRange = (
range: string
): {
start: number;
end: number;
} => {
let [start, end] = range.split("-");
return {
start: parseInt(start),
end: parseInt(end),
};
};
let parsedData = parseRange("1-5");
In the above example, we have explicitly mentioned the return type of the parseRange
function. However, even if we don't specify the return type, TypeScript will infer the return type for us.
If we try to access the start or end keys on the parsedDate
variable, we will get autosuggestion from an IDE because TypeScript knows the return type of the parseRange
function.
void
RETURN TYPE:
Similar to languages like Java, void
is used when there is no data. For example, if a function does not return any value then you can specify void
as a return type or let TypeScript infer the return type as void
.
const helloWorld = () => {
console.log("Hello World");
};
let sayHello = helloWorld();
console.log(sayHello); // this will print undefined
You will get a compilation error if you attempt to return any value other than void
after specifying void
as a return type for that function:
You can only return undefined
if you specify void
as the return type. The following image shows valid and invalid return statements for a function with the return type of void
.
Remember the Rule: "
void
is the return type of a function/method that doesn’t explicitly return anything".When no return statement is provided and no explicit return type is specified in the function or method, it infers a
void
return type automatically.
FUNCTION AS A TYPE:
It is possible to assign a type to a variable that we want to use as a function with a specific parameter and return type. Here's how to declare one:
let add: (a: number, b: number) => number;
add = (a: number, b: number) => {
return a + b;
};
In the above code, we are declaring a variable add
and have assigned a type (a: number, b: number) => number
(This is not an arrow function or a function declaration. Here, the left side of the =>
are the parameters that the function expects and the right side of the =>
is the return type of that function)
Using the add
the variable we declared earlier, we can assign a function to it as a value
.
TypeScript will throw an error stating that this variable only allows two parameters if we try to add another.
PASS Function
AS A PARAMETER:
Take a look at the following code:
const sendMessage = (cb: (a: string) => void) => {
cb("Hello, World");
};
sendMessage((str)=>{
console.log(str);
});
Here, the sendMessage
function has a parameter named cb
(callback function) with one parameter named a
, of type string
, that has no return value, since the function has been declared to have a return type of void
.
Even if you return anything inside a callback, TypeScript won't throw a compilation error, instead, it will ignore it.
DECLARE Function
TYPE INSIDE AN object
:
Earlier, we discussed the concept of an object
type, which can hold a collection of different types. You can assign a function
type to any of its keys.
GENERAL SYNTAX:
Take a look at the following code:
const person: {
name: string;
age: number;
sayHi(): void;
} = {
name: "Max",
age: 30,
sayHi: () => {
console.log("Hi");
},
};
In the above code, we have a person
object with a key named say
, and it is a function
that returns nothing.
The following is a general
way of defining a function type. The other way to declare a function in an object is with the property syntax
.
PROPERTY SYNTAX:
The sayHi
key in the above example can be declared in property syntax
, which is more commonly used and readable.
const person: {
name: string;
age: number;
sayHi: () => void;
} = {
name: "Max",
age: 30,
sayHi: () => {
console.log("Hi");
},
};
The logic remains the same but this is a more readable and preferred format of assigning a function type.
unknown
TYPE:
unknown
is not commonly used but it can be helpful to be aware of it.
WHY USE unknown
INSTEAD OF any
:
unknown
and any
are similar to some extent but unknown
is a bit more restrictive than any
. To understand this take a look at the following code:
let userInput: unknown;
let userName: string;
userInput = 5;
userInput = "John";
In the above code, we are having userInput
which is having types as unknown
and userName
with type string
Now, you can assign any value to the variable with the type unknown
. So you won't get any compilation errors for assigning 5
and "John"
to variable userInput
.
But what happens if you try to assign userInput
to userName
?
let userInput: unknown;
let userName: string;
userInput = 5;
userInput = "Max";
userName = userInput; // This line will throw an error
It says Type 'unknown' is not assignable to type 'string
even though string
has already been assigned one line before.
Since the unknown
type can only be assigned to any
type and the unknown
type itself, it can never be assigned to another type.
USE TYPE GUARDS TO AVOID ERRORS:
Adding type guards to the above code will allow it to work, as we saw in previous blogs.
let userInput: unknown;
let userName: string;
userInput = 5;
userInput = "Max";
if (typeof userInput === "string") {
userName = userInput;
}
In the above code, we are only assigning userInput
to userName
only if userInput has a string
type value.
DIFFERENCE BETWEEN unknown
AND any
:
What happens when you change the type of userInput
from unknown
to any
?
let userInput: any;
let userName: string;
userInput = 5;
userInput = "Max";
userName = userInput; // compile without an error
The main difference between unknown
and any
is that unknown
is much less permissive. We must perform some form of checking before performing most operations on values of type unknown
, but we do not have to perform any checks before performing operations on values of type any
.
never
TYPE:
As the type name suggests, never
refers to a function that will never return a value, not even an undefined
or a null
value.
DIFFERENCE BETWEEN never
AND void
:
The main difference between
never
andvoid
is thatvoid
simply returns undefined if you try to access the return value from a function withvoid
return type.However, the function that returns
never
will cause the code to crash, and the code would not be executed further.
EXAMPLE 1 - FUNCTION THAT THROWS AN ERROR:
Generally, when we throw an error from any function, its return type is inferred to be never
. Let's take a look at the following function:
const generateError = (message: string, code: number): never => {
throw {
message,
code,
};
};
const returnedValue = generateError("Something went wrong!", 500);
console.log(returnedValue);
We are throwing an error whenever we call the above function. This is what we see in the console:
We got the error we threw, but we didn't get any output from console.log(returnedValue)
, not even undefined
. It indicates that a function with a return type never caused the script to crash.
EXAMPLE 2 - INFINITE LOOP:
Another type of function that returns never
is the one with an infinite while loop.
const infiniteLoop = () => {
while (true) {}
};
const returnedValue = infiniteLoop();
console.log(returnedValue);
In the above function, the infiniteLoop
the function runs continuously, which breaks the script. If you hover over the infiniteLoop
function, you'll see that TypeScript has inferred the return type as never
.
CONCLUSION:
In TypeScript, functions are the building blocks of applications. In this article, we learned how to build type-safe functions using TypeScript, different types of annotations, how to use functions as types, and the concept of
unknown
andnever
types.Having this knowledge will allow for more type-safe and easy-to-maintain functions throughout your code.
Make sure to subscribe to our newsletter on blog.wajeshubham.in and never miss any upcoming articles related to TypeScript and programming just like this one.
I hope this post will help you in your journey. Keep learning!