Async Patterns and Fetch Functionality
Master asynchronous operations and HTTP requests with go-errors
Async Patterns
Promise Timeouts
import { goFetch } from 'go-errors';
// Handle promise timeouts
let [result, err] = await Promise.race([
goFetch('/api/data'),
new Promise<[null, Error]>(resolve =>
setTimeout(() => resolve([null, new Error('timeout')]), 5000)
)
]);
if (err?.message === 'timeout') {
console.log('Operation timed out');
}
Multiple Promise Handling
// Handle multiple promises in parallel
let [results, errors] = await Promise.all([
goFetch('/api/data1'),
goFetch('/api/data2')
]).then(results => {
const data = results.map(([d]) => d);
const errors = results.map(([_, e]) => e).filter(Boolean);
return [data, errors.length ? errors : null] as const;
});
if (errors) {
console.error('Some requests failed:', errors);
} else {
console.log('All data:', results);
}
Response Transformation
interface User {
id: number;
firstName: string;
lastName: string;
}
interface TransformedUser {
userId: string;
fullName: string;
}
let [user, err] = await goFetch<TransformedUser>('/api/user', {
responseTransformer: (data: unknown) => {
const user = data as User;
return {
userId: `user_${user.id}`,
fullName: `${user.firstName} ${user.lastName}`
};
}
});
if (err) {
console.error('Failed to fetch user:', err);
} else {
console.log('User:', user);
}
Error Transformation
interface ApiError {
code: string;
message: string;
details?: unknown;
}
let [data, err] = await goFetch<User, ApiError>('/api/user', {
errorTransformer: (error) => {
if (error instanceof Response) {
return {
code: `HTTP_${error.status}`,
message: error.statusText,
details: { status: error.status }
};
}
return {
code: 'UNKNOWN_ERROR',
message: error instanceof Error ? error.message : String(error)
};
}
});
if (err) {
console.error(`API Error (${err.code}):`, err.message);
}
Parallel Operations
interface UserProfile {
user: User;
posts: Post[];
settings: UserSettings;
}
async function fetchUserProfile(userId: string): Promise<Result<UserProfile, ApiError>> {
// Fetch all data in parallel
const [
[user, userErr],
[posts, postsErr],
[settings, settingsErr]
] = await Promise.all([
goFetch<User>(`/api/users/${userId}`),
goFetch<Post[]>(`/api/users/${userId}/posts`),
goFetch<UserSettings>(`/api/users/${userId}/settings`)
]);
// Handle any errors
if (userErr || postsErr || settingsErr) {
return [null, {
code: 'FETCH_ERROR',
message: 'Failed to fetch user profile',
details: {
user: userErr,
posts: postsErr,
settings: settingsErr
}
}] as const;
}
// Return combined data
return [{
user,
posts,
settings
}, null] as const;
}
Retry Pattern
interface RetryOptions {
maxRetries: number;
initialDelay: number;
maxDelay: number;
backoffFactor: number;
shouldRetry?: (error: unknown) => boolean;
}
async function withRetry<T, E = Error>(
operation: () => Promise<Result<T, E>>,
options: RetryOptions
): Promise<Result<T, E>> {
let delay = options.initialDelay;
let lastError: E | null = null;
for (let attempt = 0; attempt < options.maxRetries; attempt++) {
let [result, err] = await operation();
if (!err) {
return [result, null] as const;
}
lastError = err;
if (options.shouldRetry && !options.shouldRetry(err)) {
break;
}
if (attempt < options.maxRetries - 1) {
await new Promise(resolve => setTimeout(resolve, delay));
delay = Math.min(delay * options.backoffFactor, options.maxDelay);
}
}
return [null, lastError] as const;
}
// Usage with goFetch
async function fetchWithRetry<T>(url: string) {
return withRetry<T>(
() => goFetch<T>(url),
{
maxRetries: 3,
initialDelay: 1000,
maxDelay: 5000,
backoffFactor: 2,
shouldRetry: (error) => {
if (error instanceof Response) {
// Retry on network errors or 5xx server errors
return error.status >= 500;
}
return true;
}
}
);
}
let [data, err] = await fetchWithRetry<User>('/api/user/123');
Best Practices
-
Use Type Parameters
// ✅ Good: Explicit types let [user, err] = await goFetch<User, ApiError>('/api/user'); // ❌ Bad: Implicit any let [data, err] = await goFetch('/api/user');
-
Transform Responses
// ✅ Good: Transform and validate responses let [user, err] = await goFetch<User>('/api/user', { responseTransformer: (data) => { if (!isValidUser(data)) { throw new Error('Invalid user data'); } return { ...data, fullName: `${data.firstName} ${data.lastName}` }; } });
-
Handle All Error Cases
// ✅ Good: Comprehensive error handling let [data, err] = await goFetch<User, ApiError>('/api/user', { errorTransformer: (error) => ({ code: error instanceof Response ? `HTTP_${error.status}` : 'UNKNOWN', message: error instanceof Error ? error.message : String(error) }) }); if (err) { switch (err.code) { case 'HTTP_404': return handleNotFound(); case 'HTTP_401': return handleUnauthorized(); default: return handleError(err); } }