goFetch Function
A powerful wrapper around the Fetch API with Go-style error handling and built-in data transformation.
goFetch
Function
The goFetch
function provides a convenient and type-safe way to make HTTP requests, inspired by Go's error handling pattern. It wraps the native fetch
API and adds features like response and error transformation.
Type Signature
function goFetch<T, E = Error>(
input: RequestInfo | URL,
init?: GoFetchOptions<T, E>
): Promise<Result<T, E>>;
Type Parameters:
T
: The type of the transformed response data.E
: The type of the transformed error. Defaults toError
.
Parameters:
input
: The resource to fetch (same as the first argument tofetch
). Can be astring
,Request
, orURL
object.init
(optional): AnGoFetchOptions
object, which extends the standardRequestInit
object with additional options forgoFetch
.
Returns:
Promise<Result<T, E>>
: A Promise that always resolves to aResult<T, E>
tuple:[data, null]
if the request is successful (and theresponseTransformer
completes successfully).[null, error]
if the request fails or theresponseTransformer
throws an error.
GoFetchOptions
Interface
interface GoFetchOptions<T, E = Error> extends RequestInit {
responseTransformer?: (data: unknown) => T;
errorTransformer?: (error: unknown) => E;
}
responseTransformer?: (data: unknown) => T
: An optional function that transforms the raw response data (after being parsed as JSON if theContent-Type
isapplication/json
) into the desired typeT
. If not provided,goFetch
attempts to parse the response as JSON and returns the result asunknown
. If the response cannot be parsed as JSON, the rawResponse
object is passed to theresponseTransformer
.errorTransformer?: (error: unknown) => E
: An optional function that transforms any error encountered during the fetch operation (network errors, HTTP errors, errors thrown byresponseTransformer
) into the desired error typeE
. If not provided, the error is normalized to anError
object.
All standard RequestInit
options are also supported, such as method
, headers
, body
, mode
, credentials
, cache
, etc.
Basic Usage
import { goFetch } from 'go-errors';
async function main() {
let [data, err] = await goFetch('/api/data'); // Simple GET request
if (err) {
console.error("Fetch failed:", err);
} else {
console.log("Data:", data); // data is of type 'unknown'
}
}
main();
responseTransformer
Type-Safe Usage with import { goFetch } from 'go-errors';
interface User {
id: number;
name: string;
email: string;
}
async function getUser() {
let [user, err] = await goFetch<User>('/api/users/123', {
responseTransformer: (data: any) => {
// You can perform validation and transformation here
if (typeof data !== 'object' || data === null || !data.id || !data.name || !data.email) {
throw new Error("Invalid user data received");
}
return {
id: data.id,
name: data.name,
email: data.email,
};
},
});
if (err) {
console.error("Failed to fetch user:", err);
} else {
console.log("User:", user); // user is of type User
}
}
getUser();
Explanation:
- We use
goFetch<User>
to specify that we expect the transformed response data to be of typeUser
. - The
responseTransformer
function receives the raw response data (already parsed as JSON if theContent-Type
isapplication/json
). - Inside
responseTransformer
, we perform validation and transform the data into the desiredUser
object. If the data is invalid, we throw an error. - If
responseTransformer
throws an error,goFetch
will catch it and return aResult
tuple with the error.
errorTransformer
Handling Errors with import { goFetch } from 'go-errors';
class ApiError extends Error {
constructor(message: string, public status: number, public code?: string) {
super(message);
this.name = 'ApiError';
}
}
async function getData() {
let [data, err] = await goFetch<MyDataType, ApiError>('/api/data', {
errorTransformer: (error) => {
if (error instanceof Response) {
// Handle HTTP errors
return new ApiError(`HTTP Error: ${error.status}`, error.status);
}
if (error instanceof Error) {
// Handle other errors (e.g., network errors, errors from responseTransformer)
return new ApiError(error.message, 500); // Default to 500 for other errors
}
// Handle unexpected error types
return new ApiError("An unknown error occurred", 500);
},
});
if (err) {
if (err instanceof ApiError) {
console.error("API Error:", err.message, "Status:", err.status);
} else {
console.error("Unexpected Error:", err); // This should ideally not happen
}
} else {
console.log("Data:", data);
}
}
getData();
Explanation:
- We define a custom
ApiError
class to represent API errors. - We use
goFetch<MyDataType, ApiError>
to specify the expected data type and the custom error type. - The
errorTransformer
function receives the raw error (which could be aResponse
object, anError
object, or something else). - Inside
errorTransformer
, we check the type of the error and transform it into anApiError
instance. - This allows us to handle all errors in a consistent way, using our custom
ApiError
type.
POST, PUT, DELETE Requests
async function createUser() {
let [user, err] = await goFetch<User>('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name: 'John Doe', email: 'john.doe@example.com' }),
responseTransformer: (data: any) => data as User, // Simple transformation
});
if (err) {
console.error("Failed to create user:", err);
} else {
console.log("Created user:", user);
}
}
responseTransformer
and errorTransformer
Combining interface User {
id: number;
name: string;
email: string;
}
class ValidationError extends Error {
constructor(message: string, public field: string) {
super(message);
this.name = 'ValidationError';
}
}
async function updateUser(id: number, updates: Partial<User>) {
let [updatedUser, err] = await goFetch<User, ValidationError | ApiError>(`/api/users/${id}`, {
method: 'PATCH', // Or 'PUT'
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(updates),
responseTransformer: (data: any) => {
if (!data.id || !data.name || !data.email) {
throw new ValidationError("Invalid user data received", "response");
}
return data as User;
},
errorTransformer: (error) => {
if (error instanceof ValidationError) {
return error; // Already a ValidationError
}
if (error instanceof Response) {
return new ApiError(`HTTP Error: ${error.status}`, error.status);
}
return new ApiError("An unknown error occurred", 500);
},
});
if (err) {
if (err instanceof ValidationError) {
console.error("Validation Error:", err.message, "Field:", err.field);
} else if (err instanceof ApiError) {
console.error("API Error:", err.message, "Status:", err.status);
} else {
console.error("Unexpected Error:", err);
}
} else {
console.log("Updated user:", updatedUser);
}
}
Best Practices
- Use Type Parameters: Always specify the
T
andE
type parameters forgoFetch
to leverage TypeScript's type safety. - Use
responseTransformer
: UseresponseTransformer
to validate and transform the response data into the expected type. This ensures that you're working with data in the correct format. - Use
errorTransformer
: UseerrorTransformer
to transform errors into custom error types or a consistent error format. This centralizes error handling and makes your code more maintainable. - Handle All Error Cases: In your
errorTransformer
, handle different types of errors (e.g.,Response
objects for HTTP errors,Error
objects for network or other errors). - Early Returns: Use early returns (
if (err) return [null, err] as const;
) to handle errors as soon as they occur. - Custom Error Classes: Define custom error classes that extend
Error
to provide more context and structure to your errors. - Always use
let
: Uselet
for result declarations.
See Also
- go Function: For general asynchronous operations.
- goSync Function: For synchronous operations.
- The Result Type: Detailed explanation of the
Result
type. - Error Handling in go-errors: Comprehensive guide to error handling.
- Working with Custom Error Types: Defining and using custom error types.
- MDN Documentation for Fetch API: For details on the underlying
fetch
API.