Async Programming in TypeScript
TypeScript adds type safety to JavaScript's asynchronous programming features, making it easier to work with Promises, async/await, and callbacks.
Promises
TypeScript can type Promise results:
// Basic Promise type
const fetchData: Promise<string> = new Promise((resolve) => {
setTimeout(() => resolve("Hello"), 1000);
});
// Function returning a Promise
async function getData(): Promise<string> {
const response = await fetch('https://api.example.com/data');
return response.text();
}
Async/Await with Types
Type-safe async operations:
interface User {
id: number;
name: string;
email: string;
}
async function getUser(id: number): Promise<User> {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new Error('User not found');
}
return response.json();
}
// Using the async function
try {
const user = await getUser(1);
console.log(user.name); // TypeScript knows user has name
} catch (error) {
console.error(error);
}
Error Handling
Type-safe error handling in async code:
class APIError extends Error {
constructor(
public statusCode: number,
message: string
) {
super(message);
}
}
async function fetchUser(id: number): Promise<User> {
try {
const response = await fetch(`/api/users/${id}`);
if (!response.ok) {
throw new APIError(response.status, 'Failed to fetch user');
}
return response.json();
} catch (error) {
if (error instanceof APIError) {
// Handle API-specific error
console.error(`API Error ${error.statusCode}: ${error.message}`);
}
throw error;
}
}
Concurrent Operations
Type-safe parallel execution:
interface Task {
id: string;
result: number;
}
async function processTasks(tasks: Task[]): Promise<number[]> {
// Process all tasks concurrently
const promises = tasks.map(async (task) => {
const result = await processTask(task);
return result;
});
// Wait for all to complete
return Promise.all(promises);
}
// Using with type inference
const results = await processTasks([
{ id: "1", result: 42 },
{ id: "2", result: 84 }
]);
Best Practices
- Always specify Promise return types
- Use try/catch blocks for error handling
- Consider Promise.all for concurrent operations
- Handle edge cases and timeouts
- Keep async functions focused and small
Common Patterns
Loading States
interface State<T> {
data: T | null;
loading: boolean;
error: Error | null;
}
function createInitialState<T>(): State<T> {
return {
data: null,
loading: false,
error: null
};
}
async function fetchWithState<T>(
url: string,
state: State<T>
): Promise<State<T>> {
try {
state.loading = true;
const response = await fetch(url);
state.data = await response.json();
return state;
} catch (error) {
state.error = error as Error;
return state;
} finally {
state.loading = false;
}
}
Practice Exercise
Create a type-safe async task queue that:
- Handles different types of tasks
- Processes tasks in order
- Includes proper error handling
- Reports progress with TypeScript types