The author choosesCOVID-19 Relief FundAs aWrite for DOnationsPart of the program accepts donations.

Introduction to the

Creating and using functions is a fundamental aspect of any programming language, and TypeScript is no exception. TypeScript fully supports functions with existing JavaScript syntax, while adding new features for type information and function overloading. In addition to providing additional documentation for functions, type information also reduces the chance of errors in your code, because there is less risk of passing invalid data types to type-safe functions.

In this tutorial, you’ll start by creating the most basic functions with type information, then move on to more complex scenarios such as using rest arguments and function overloading. You’ll try out different code samples that you can follow in your own TypeScript environment or TypeScript Playground, an online environment that allows you to write TypeScript directly in your browser.

A prerequisite for

To follow this tutorial, you will need.

  • An environment in which you can execute TypeScript programs to follow the example as it develops. To set this up on your local machine, you need the following.
    • Install Both Node and NPM (or YARN) to run a development environment that handles typescript-related packages. This tutorial was tested on Node.js version 14.3.0 and NPM version 6.14.5. To install on macOS or Ubuntu 18.04, follow the steps in “How to Install Node.js on macOS and Create a Local Development Environment” or follow the “Installing with PPA” section in “How to Install Node.js on Ubuntu 18.04”. The same applies if you use Windows Subsystem for Linux (WSL).
    • In addition, you will need to install the TypeScript compiler on your machine.tsc). To do this, please refer toThe official TypeScript website.
  • If you don’t want to create a TypeScript environment on your local machine, you can use the official TypeScript Playground to learn.
  • You need to have a good knowledge of JavaScript, especially ES6+ syntax such as structuring, rest operators, and import/export. If you need more information on these topics, we recommend reading our how to Program in JavaScript series.
  • This tutorial references various aspects of typescript-enabled text editors and displays inline errors. This isn’t necessary to use TypeScript, but it does allow you to take advantage of TypeScript’s features. To get these benefits, you can use a text editor like Visual Studio Code, which has full support for TypeScript out of the box. You can also try these advantages in TypeScript Playground.

All of the examples shown in this tutorial are created using TypeScript version 4.2.2.

Create typed functions

In this section, you create functions in TypeScript and add type information to them.

In JavaScript, you can declare functions in a variety of ways. One of the most popular is to use the function keyword, as shown in the figure below.

function sum(a, b) {
  return a + b;
}
Copy the code

In this example, sum is the name of the function, (a, b) is the argument, {return a + b; } is the function body.

The syntax for creating functions in TypeScript is the same, except for one major addition. You can let the compiler know what type each argument should have. The following code block shows the general syntax for this and highlights the type declaration.

function functionName(param1: Param1Type, param2: Param2Type) :ReturnType {
  // ... body of the function
}
Copy the code

Using this syntax, you can add types to the arguments to the sum function shown earlier.

function sum(a: number, b: number) {
  return a + b;
}
Copy the code

This ensures that a and b are the values of number.

You can also add the type of return value.

function sum(a: number, b: number) :number {
  return a + b;
}
Copy the code

Now TypeScript expects the sum function to return a numeric value. If you call your function with arguments and store the result value in a variable called result.

const result = sum(1.2);
Copy the code

The result variable will be of type number. If you’re using a TypeScript playground or a fully typescript-enabled text editor, hovering over result will display const result: number, indicating that TypeScript has already implied its type from the function declaration.

If you call your function with a value that is not of the type your function expects, the TypeScript compiler (TSC) will give you error 2345. The following is an example of a call to sum.

sum('shark'.'whale');
Copy the code

This yields the following results.

OutputArgument of type 'string' is not assignable to parameter of type 'number'. (2345)
Copy the code

You can use any type in your functions, not just basic types. For example, imagine you have a User that looks something like this.

type User = {
  firstName: string;
  lastName: string;
};
Copy the code

You can create a function to return the user’s full name, as shown below.

function getUserFullName(user: User) :string {
  return `${user.firstName} ${user.lastName}`;
}
Copy the code

Most of the time, TypeScript is smart enough to infer the return type of a function, so in this case, you can remove the return type from the function declaration.

function getUserFullName(user: User) {
  return `${user.firstName} ${user.lastName}`;
}
Copy the code

Notice that you removed the: string part, which is the return type of your function. Since you return a string in the body of a function, TypeScript correctly assumes that your function has a string return type.

Now to call your function, you must pass an object of the same shape as the User type.

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User) {
  return `${user.firstName} ${user.lastName}`;
}

const user: User = {
  firstName: "Jon".lastName: "Doe"
};

const userFullName = getUserFullName(user);
Copy the code

This code will pass the TypeScript type checker successfully. If you hover over the userFullName constant in the editor, the editor will recognize that it is of type String.

Optional function arguments in TypeScript

It is not always necessary to have all the parameters when creating a function. In this section, you’ll learn how to mark function parameters as optional in TypeScript.

To make a function argument an optional argument, add? Modifier. Given a function parameter of type T param1, you can add? Make param1 optional, as shown in the following figure.

param1? : TCopy the code

For example, add an optional prefix argument to your getUserFullName function, which is an optional string that can be added as a prefix to the user’s full name.

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User, prefix? :string) {
  return `${prefix ?? ' '}${user.firstName} ${user.lastName}`;
}
Copy the code

In the first highlighted section of this code block, you are adding an optional prefix argument to your function, and in the second highlighted section, you are using it to prefix the user’s full name. To do this, you use the Nullish condensation operator?? . Thus, you will only use this value if prefix is defined; Otherwise, the function uses an empty string.

You can now call your function with or without prefix arguments, as shown below.

type User = {
  firstName: string;
  lastName: string;
};

function getUserFullName(user: User, prefix? :string) {
  return `${prefix ?? ' '} ${user.firstName} ${user.lastName}`;
}

const user: User = {
  firstName: "Jon".lastName: "Doe"
};

const userFullName = getUserFullName(user);
const mrUserFullName = getUserFullName(user, 'Mr. ');
Copy the code

In this case, the value of userFullName would be Jon Doe, and the value of mrUserFullName would be Mr. Jon Doe.

Note that you cannot add an optional parameter before a required one; It must be listed at the end of the series, as in (user: user, prefix? : string). Listing it first causes the TypeScript compiler to return error 1016.

OutputA required parameter cannot follow an optional parameter. (1016)
Copy the code

Typed arrow function expression

So far, this tutorial has shown how to type normal functions in TypeScript, defined with the function keyword. But in JavaScript, you can define a function in more than one way, like with the arrow function. In this section, you add types to arrow functions in TypeScript.

The syntax for adding a type to an arrow function is almost the same as adding a type to a normal function. To illustrate this, change your getUserFullName function to an arrow function expression.

const getUserFullName = (user: User, prefix? :string) = > `${prefix ?? ' '}${user.firstName} ${user.lastName}`;
Copy the code

If you want to specify the return type of your function, you can highlight it in (), as shown in the code below.

constgetUserFullName = (user: User, prefix? :string) :string= > `${prefix ?? ' '}${user.firstName} ${user.lastName}`;
Copy the code

Now you can use your functions as before.

type User = {
  firstName: string;
  lastName: string;
};

const getUserFullName = (user: User, prefix? :string) = > `${prefix ?? ' '}${user.firstName} ${user.lastName}`;

const user: User = {
  firstName: "Jon".lastName: "Doe"
};

const userFullName = getUserFullName(user);
Copy the code

This passes through the TypeScript type checker without any errors.

** Note: ** Remember that everything that works for functions in JavaScript works for functions in TypeScript. To review these rules, check out our tutorial how to Define Functions in JavaScript.

Function types

In the previous section, you added types to arguments and return values of TypeScript functions. In this section, you will learn how to create function types, that is, types that represent the signature of a particular function. Creating a type that matches a particular function is especially useful when passing functions to other functions, such as one argument that is itself a function. This is a common pattern when creating functions that accept callbacks.

The syntax for creating your function type is similar to that for creating arrow functions, with two differences.

  • You removed the body of the function.
  • You let the function declare returnreturnThe type itself.

Here’s how you can create a type that matches the getUserFullName function you’ve been using.

type User = {
  firstName: string;
  lastName: string;
};

type PrintUserNameFunction = (user: User, prefix? :string) = > string;
Copy the code

In this example, you declare a new type with the type keyword, then provide the type of two parameters in parentheses, and the type of the return value after the arrow.

For a more concrete example, imagine that you are creating an event listener function called onEvent that takes the event name as its first argument and the event callback as its second argument. The event callback itself will receive an object of the following types as its first argument.

type EventContext = {
  value: string;
};
Copy the code

And then you can write your onEvent function like this.

type EventContext = {
  value: string;
};

function onEvent(eventName: string, eventCallback: (target: EventContext) => void) {
  // ... implementation
}
Copy the code

Note that the type of the eventCallback parameter is a function type.

eventCallback: (target: EventTarget) = > void
Copy the code

This means that your onEvent function expects another function to be passed in the eventCallback argument. This function should take a single parameter of type EventTarget. The return type of this function is ignored by your onEvent function, so you use void as the type.

Use typed asynchronous functions

It is common to have asynchronous functions when using JavaScript. TypeScript has a specific way of dealing with this problem. In this section, you create asynchronous functions in TypeScript.

The syntax for creating asynchronous functions is the same as for JavaScript, with the addition of the allowed types.

async function asyncFunction(param1: number) {
  // ... function implementation ...
}
Copy the code

There is one major difference between adding a type to a normal function and adding a type to an asynchronous function. In asynchronous functions, the return type must always be Promise

generic. The Promise

generic represents the Promise object returned by the asynchronous function, where T is the type of the value that the Promise resolves.

Imagine that you have a User type.

type User = {
  id: number;
  firstName: string;
};
Copy the code

Also imagine that you have some user objects in a data store. This data can be stored anywhere, such as in a file, database, or behind an API request. For simplicity, you will use an array in this example.

type User = {
  id: number;
  firstName: string;
};

const users: User[] = [
  { id: 1.firstName: "Jane" },
  { id: 2.firstName: "Jon"}];Copy the code

If you want to create a type-safe function that asynchronously retrieves the user by ID, you can do so.

async function getUserById(userId: number) :Promise<User | null> {
  const foundUser = users.find(user= > user.id === userId);

  if(! foundUser) {return null;
  }

  return foundUser;
}
Copy the code

In this function, you first declare your function to be asynchronous.

async function getUserById(userId: number) :Promise<User | null> {
Copy the code

Then you specify that it accepts the user ID as the first argument, which must be a number.

async function getUserById(userId: number) :Promise<User | null> {
Copy the code

The return type of getUserById is a Promise, which can be resolved to User or NULL. You are using joint type User | null as Promise generic type parameters.

User | null is the Promise of < T > T.

async function getUserById(userId: number) :Promise<User | null> {
Copy the code

Call your function with await and store the result in a variable called user.

type User = {
  id: number;
  firstName: string;
};

const users: User[] = [
  { id: 1.firstName: "Jane" },
  { id: 2.firstName: "Jon"}];async function getUserById(userId: number) :Promise<User | null> {
  const foundUser = users.find(user= > user.id === userId);

  if(! foundUser) {return null;
  }

  return foundUser;
}

async function runProgram() {
  const user = await getUserById(1);
}
Copy the code

** Note: ** you are using a wrapper function called runProgram because you cannot use await at the top level of the file. Doing so causes the TypeScript compiler to issue error 1375.

Output'await' expressions are only allowed at the top level of a file when that file is a module, but this file has no imports or exports. Consider adding an empty 'export {}' to make this file a module. (1375)
Copy the code

If you are in the editor or TypeScript hovering in the user in the Playground, you will find that the type of user is the user | null, this is your commitments getUserById function returns the type of parsing.

If you call the function directly without await, it returns a Promise object.

async function runProgram() {
  const userPromise = getUserById(1);
}
Copy the code

If you hover over userPromise, you will find that it is of type Promise < User | null >.

In most cases, TypeScript can infer the return type of your asynchronous function just as it does for non-asynchronous functions. So, you can omit getUserById function return type, because it is still correct inference for a Promise < User | null > type.

async function getUserById(userId: number) {
  const foundUser = users.find(user= > user.id === userId);

  if(! foundUser) {return null;
  }

  return foundUser;
}
Copy the code

Add a type for the rest parameter

Rest arguments are a feature in JavaScript that allows a function to accept many arguments as a single array. In this section, you’ll use TypeScript rest parameters.

Using the rest argument in a type-safe manner can be done by using the type of the result array after the rest argument. For example, in the code below, you have a function called sum that takes a variable number of numbers and returns their sum.

function sum(. args:number[]) {
  return args.reduce((accumulator, currentValue) = > {
    return accumulator + currentValue;
  }, 0);
}
Copy the code

This function uses the. Reduce Array method to iterate through the Array and add the elements. Notice the remaining parameters highlighted here, args. The type is set to an array of numbers: number[].

Calling your function works fine.

function sum(. args:number[]) {
  return args.reduce((accumulator, currentValue) = > {
    return accumulator + currentValue;
  }, 0);
}

const sumResult = sum(2.4.6.8);
Copy the code

If you call your function with anything other than numbers, for example.

const sumResult = sum(2."b".6.8);
Copy the code

The TypeScript compiler issues error 2345.

OutputArgument of type 'string' is not assignable to parameter of type 'number'. (2345)
Copy the code

Using function overloading

Programmers sometimes need a function to take different arguments, depending on how the function is called. In JavaScript, this is usually done by having a parameter that can take on different types of values, such as a string or a number. Setting multiple implementations of the same function name is called _ function overloading _.

With TypeScript, you can create function overloads that explicitly describe the different situations they resolve, improving the developer experience by documenting each implementation of the overloaded function separately. This section shows you how to use function overloading in TypeScript.

Imagine that you have a User type.

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};
Copy the code

You want to create a function that queries the user with any of the following information.

  • id
  • email
  • agefullName

You can create a function like this.

function getUser(idOrEmailOrAge: number | string, fullName? :string) :User | undefined {
  // ... code
}
Copy the code

This function using the | operator for idOrEmailOrAge and return value of a type of alliance.

Next, add function overloading for each way you want your functions to be used, as shown in the highlighted code below.

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};

function getUser(id: number) :User | undefined;
function getUser(email: string) :User | undefined;
function getUser(age: number, fullName: string) :User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName? :string) :User | undefined {
  // ... code
}
Copy the code

This function has three overloads, one for each way of retrieving the user. When creating a function overload, you add it before the function implementation itself. Function overloading has no body; They only have argument lists and return types.

Next, you implement the function itself, which should have a list of arguments compatible with all function overloading. In the previous example, your first argument could be a number or a string, because it could be ID, email, or age.

function getUser(id: number) :User | undefined;
function getUser(email: string) :User | undefined;
function getUser(age: number, fullName: string) :User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName? :string) :User | undefined {
  // ... code
}
Copy the code

So, you in your function to realize the idOrEmailorAge parameter type is set to number | string. This way, it is compatible with all overloading of your getUser function.

You also add an optional argument to your function when the user passes a fullName.

function getUser(id: number) :User | undefined;
function getUser(email: string) :User | undefined;
function getUser(age: number, fullName: string) :User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName? :string) :User | undefined {
  // ... code
}
Copy the code

You can implement your functions like this, where you use a Users array as the data store for the users.

type User = {
  id: number;
  email: string;
  fullName: string;
  age: number;
};

const users: User[] = [
  { id: 1.email: "[email protected]".fullName: "Jane Doe" , age: 35 },
  { id: 2.email: "[email protected]".fullName: "Jon Doe".age: 35}];function getUser(id: number) :User | undefined;
function getUser(email: string) :User | undefined;
function getUser(age: number, fullName: string) :User | undefined;

function getUser(idOrEmailOrAge: number | string, fullName? :string) :User | undefined {
  if (typeof idOrEmailOrAge === "string") {
    return users.find(user= > user.email === idOrEmailOrAge);
  }

  if (typeof fullName === "string") {
    return users.find(user= > user.age === idOrEmailOrAge && user.fullName === fullName);
  } else {
    return users.find(user= >user.id === idOrEmailOrAge); }}const userById = getUser(1);
const userByEmail = getUser("[email protected]");
const userByAgeAndFullName = getUser(35."Jon Doe");
Copy the code

In this code, if idOrEmailOrAge is a string, you can search for users using the email key. The following condition assumes that idOrEmailOrAge is a number, which is either ID or age, depending on whether fullName is defined.

One interesting aspect of function overloading is that in most editors, including VS Code and TypeScript Playground, as soon as you call the function by typing in the function name and opening the first parentheses, a pop-up window shows all the available overloads, as shown below.

If you add a comment for each function overload, that comment will also be the document source in the popup window. For example, add the following highlighted comment to the sample reload.

./** * Get a user by their ID. */
function getUser(id: number) :User | undefined;
/** * Get a user by their email. */
function getUser(email: string) :User | undefined;
/** * Get a user by their age and full name. */
function getUser(age: number, fullName: string) :User | undefined; .Copy the code

Now, when you hover over these functions, each overloaded comment is displayed, as shown in the animation below.

User-defined type guard

The final feature of TypeScript functions that this tutorial explores is _ user-defined type_protection, which is a special function that allows TypeScript to better infer the types of certain values. These guards enforce certain types in conditional code blocks, and the type of one of the values may vary from case to case. These are especially useful when using the array.prototype. filter function to return a filtered Array of data.

When adding values to an array conditionally, a common task is to check some conditions and then add values only if the condition is true. If the value is not true, the code adds a false Boolean value to the array. Before using this array, you can use.filter(Boolean) to ensure that only real values are returned.

When called with a value, the Boolean constructor returns true or false, depending on whether the value is Truthy or Falsy.

For example, imagine that you have an array of strings and you only want to include the string production in the array if some other flag is true.

const isProduction = false

const valuesArray = ['some-string', isProduction && 'production']

function processArray(array: string[]) {
  // do something with array
}

processArray(valuesArray.filter(Boolean))
Copy the code

While this is perfectly valid code at runtime, the TypeScript compiler gives you error 2345 during compilation.

OutputArgument of type '(string | boolean)[]' is not assignable to parameter of type 'string[]'. Type 'string | boolean'  is not assignable to type 'string'. Type 'boolean' is not assignable to type 'string'. (2345)Copy the code

This error is said, at compile time, passed to the processArray value is interpreted as a false | string value in the array, it is not the processArray expected. It expects an array of strings: String [].

This is a case where TypeScript is not smart enough to infer that by using.filter(Boolean), you will delete all falsy values from the array. However, there is a way to give TypeScript this hint: use user-defined type protection.

Create a user-defined type protection function called isString.

function isString(value: any) :value is string {
  return typeof value === "string"
}
Copy the code

Notice the return type of the isString function. The way to create a user-defined type guard is to use the following syntax as the return type of a function.

parameterName is Type
Copy the code

Where parameterName is the name of the parameter you are testing and Type is the expected value of the parameter if the function returns true.

In this case, you’re saying that if isString returns true, then value is a string. You also set the type of your value parameter to any, so it works with values of type any.

Now, change your call to.filter and use your new function instead of passing it to the Boolean constructor.

const isProduction = false

const valuesArray = ['some-string', isProduction && 'production']

function processArray(array: string[]) {
  // do something with array
}

function isString(value: any) :value is string {
  return typeof value === "string"
}

processArray(valuesArray.filter(isString))
Copy the code

Now, the TypeScript compiler correctly deduces that the array passed to processArray contains only strings, and your code compiles correctly.

conclusion

Functions are part of your application in TypeScript. In this tutorial, you learned how to build type-safe functions in TypeScript and how to take advantage of function overloading to better document all variations of a single function. This knowledge will lead to more type-safe and maintainable functions in your code.

For more tutorials on TypeScript, check out our How to Code in TypeScript series of pages.