TypeScript Callbacks

Pass functions as arguments with type safety

🔄 What are Callbacks?

Callbacks are functions passed as arguments to other functions and executed later. TypeScript allows you to define callback types, ensuring the passed functions have correct parameters and return types for safer asynchronous operations.


// Function that accepts a callback
function processData(data: number, callback: (result: number) => void): void {
    const result = data * 2;
    callback(result);
}

processData(5, (result) => {
    console.log(`Result: ${result}`);
});
                                    

Output:

Result: 10

Callback Concepts

📞

Function Parameters

Pass functions as arguments

function execute(
    callback: () => void
) {
    callback();
}
🎯

Type Safety

Define callback signatures

type Callback = (
    data: string
) => void;
⏱️

Async Operations

Handle delayed execution

setTimeout(() => {
    console.log("Done!");
}, 1000);
🔗

Event Handling

Respond to events

button.addEventListener(
    "click",
    () => { }
);

🔹 Basic Callback Example

Create a function that accepts a callback:

// Function with callback parameter
function greetUser(name: string, callback: (message: string) => void): void {
    const greeting = `Hello, ${name}!`;
    callback(greeting);
}

// Call with callback
greetUser("Alice", (message) => {
    console.log(message);
});

// Call with different callback
greetUser("Bob", (message) => {
    console.log(`Message received: ${message}`);
});

Output:

Hello, Alice!

Message received: Hello, Bob!

🔹 Callbacks with Return Values

Callbacks can return values:

// Callback that returns a value
function calculate(
    a: number, 
    b: number, 
    operation: (x: number, y: number) => number
): number {
    return operation(a, b);
}

// Different operations
const sum = calculate(10, 5, (x, y) => x + y);
const product = calculate(10, 5, (x, y) => x * y);
const difference = calculate(10, 5, (x, y) => x - y);

console.log("Sum:", sum);
console.log("Product:", product);
console.log("Difference:", difference);

Output:

Sum: 15

Product: 50

Difference: 5

🔹 Callback Type Aliases

Define reusable callback types:

// Define callback types
type SuccessCallback = (data: string) => void;
type ErrorCallback = (error: string) => void;

function fetchData(
    url: string,
    onSuccess: SuccessCallback,
    onError: ErrorCallback
): void {
    // Simulate API call
    const success = url.includes("valid");
    
    if (success) {
        onSuccess("Data loaded successfully!");
    } else {
        onError("Failed to load data");
    }
}

// Use the function
fetchData(
    "valid-url",
    (data) => console.log("Success:", data),
    (error) => console.log("Error:", error)
);

fetchData(
    "invalid-url",
    (data) => console.log("Success:", data),
    (error) => console.log("Error:", error)
);

Output:

Success: Data loaded successfully!

Error: Failed to load data

🔹 Array Methods with Callbacks

Common array methods use callbacks:

const numbers: number[] = [1, 2, 3, 4, 5];

// forEach callback
numbers.forEach((num: number, index: number) => {
    console.log(`Index ${index}: ${num}`);
});

console.log("---");

// map callback
const doubled = numbers.map((num: number) => num * 2);
console.log("Doubled:", doubled);

// filter callback
const evens = numbers.filter((num: number) => num % 2 === 0);
console.log("Evens:", evens);

Output:

Index 0: 1

Index 1: 2

Index 2: 3

Index 3: 4

Index 4: 5

---

Doubled: [2, 4, 6, 8, 10]

Evens: [2, 4]

🔹 Asynchronous Callbacks

Use callbacks for async operations:

// Simulate async operation
function loadUser(
    userId: number,
    callback: (user: { id: number; name: string }) => void
): void {
    console.log("Loading user...");
    
    // Simulate delay
    setTimeout(() => {
        const user = { id: userId, name: "Alice" };
        callback(user);
    }, 1000);
}

loadUser(1, (user) => {
    console.log(`User loaded: ${user.name} (ID: ${user.id})`);
});

Output:

Loading user...

(after 1 second)

User loaded: Alice (ID: 1)

🔹 Practical Example: Event System

Build a simple event system with callbacks:

type EventCallback = (data: any) => void;

class EventEmitter {
    private events: { [key: string]: EventCallback[] } = {};
    
    on(event: string, callback: EventCallback): void {
        if (!this.events[event]) {
            this.events[event] = [];
        }
        this.events[event].push(callback);
    }
    
    emit(event: string, data: any): void {
        if (this.events[event]) {
            this.events[event].forEach(callback => callback(data));
        }
    }
}

const emitter = new EventEmitter();

emitter.on("userLogin", (username: string) => {
    console.log(`User logged in: ${username}`);
});

emitter.on("userLogin", (username: string) => {
    console.log(`Welcome back, ${username}!`);
});

emitter.emit("userLogin", "Alice");

Output:

User logged in: Alice

Welcome back, Alice!

💡 Callback Best Practices:

  • Always define callback parameter and return types
  • Use type aliases for complex callback signatures
  • Consider using Promises or async/await for complex async operations
  • Avoid "callback hell" by keeping nesting shallow
  • Handle errors appropriately in callbacks

🧠 Test Your Knowledge

What is a callback function?