Understand how to type functions, use callbacks, and handle advanced parameter patterns.
In TypeScript, functions can be typed to ensure they receive the correct inputs and return expected outputs. Properly typing functions makes code more maintainable and reduces errors.
function greet(name: string, age?: number): string {
return `Hello ${name}! You are ${age ?? 'unknown'} years old.`;
}
greet('Alice'); // Works
console.log(greet('Bob', 30));
Use the optional operator (?) to mark parameters that are not required. You can also provide default values using =.
interface User {
name: string;
age?: number;
}
function createUser(name: string, age = 18): User {
return { name, age };
}
const user = createUser('Charlie');
console.log(user.age); // 18
Functions that accept other functions as arguments or return new functions are called higher-order functions. They're essential for creating reusable and flexible code.
function createLogger(level: string) {
return function(message: string, context: string = 'default') {
console.log(`${level}: ${message} (context: ${context})`);
};
}
const debugLogger = createLogger('DEBUG');
debugLogger('Something happened');
Callbacks allow functions to °handle asynchronous operations§. They're widely used in Node.js and NestJS for operations like database queries.
function processRequest(request: any, callback: (result: string, error?: Error) => void) {
try {
const result = `Processed ${request}`;
callback(result);
} catch (error) {
callback(null, error);
}
}
processRequest('data', (result, error) => {
if (error) {
console.error('Error:', error);
} else {
console.log('Success:', result);
}
});
async function processRequest(request: any) {
try {
return `Processed ${request}`;
} catch (error) {
throw new Error(`Processing failed: ${error.message}`);
}
}
processRequest('data').then(result =>
console.log('Success:', result)
).catch(error =>
console.error('Error:', error)
);
Function overloading is a powerful TypeScript feature that allows you to define multiple implementations of the same function with different parameters. This enables your functions to accept various argument signatures while maintaining type safety and clarity.
When working with variadic functions, you often need to handle different numbers of arguments. TypeScript's rest parameters (denoted by `...args`) allow you to capture multiple arguments into an array while preserving type information.
function print(message: string): void;
function print(message: string, options: { verbose?: boolean }): void;
function print(message: string, options?: { verbose?: boolean }) {
if (options?.verbose) {
console.log(` VERBOSE: ${message}`);
} else {
console.log(message);
}
}
Important: Overload signatures must be placed before the implementation signature and should not include a function body.
function sum(...numbers: number[]): number {
return numbers.reduce((acc, num) => acc + num, 0);
}
sum(1, 2, 3); // 6
sum(5); // 5
function merge(a: string, b: string): string;
function merge(a: any[], b: any[]): any[];
function merge(a: string | any[], b: string | any[]) {
if (typeof a === 'string') {
return a + b;
}
return [...a, ...b];
}
This example demonstrates how to handle both string concatenation and array merging with a single function.
function validate(value: string, rules: { required: boolean }): string;
function validate(value: string, rules: { pattern: RegExp }): string;
function validate(value: string, rules: { required?: boolean; pattern?: RegExp }) {
if (rules.required && !value) return 'Field is required';
if (rules.pattern && !rules.pattern.test(value)) return 'Invalid format';
return value;
}
This validation function handles both presence checks and regex patterns depending on the rules provided.
In this chapter, we'll explore three essential TypeScript types that help you write safer and more maintainable code: void, never, and unknown. These might seem rare at first glance, but they're crucial for handling specific scenarios in your applications.
The void type is used to explicitly indicate that a function does not return any value. While TypeScript can infer this automatically, using void makes your intentions clear to other developers.
function logMessage(message: string): void {
console.log(message);
}
// You can also omit the return type since TypeScript will default to void
function logMessageWithoutReturnType(message: string) {
console.log(message);
}
The never type represents functions that never complete normally. This could be because they throw an error or enter an infinite loop.
function handleError(message: string): never {
throw new Error(message);
}
// Example of a function that will never return
while (true) {
console.log('This will run forever');
}
The unknown type is a safer alternative to any. It represents values whose types are not known at compile time but must be checked before use.
function handleData(data: unknown): void {
if (typeof data === 'string') {
console.log('Data is a string:', data);
} else if (Array.isArray(data)) {
console.log('Data is an array with length:', data.length);
}
}
Understanding these types is essential for building robust applications with NestJS. For example, when creating API endpoints that may receive malformed data, using unknown ensures you properly validate inputs before processing them.
Question 1 of 15