166 lines
4.6 KiB
Markdown
166 lines
4.6 KiB
Markdown
---
|
|
title: Handle SDK Errors Properly
|
|
impact: HIGH
|
|
impactDescription: Graceful error recovery, better UX, easier debugging
|
|
tags: sdk, errors, error-handling, exceptions
|
|
---
|
|
|
|
## Handle SDK Errors Properly
|
|
|
|
All SDK methods return Promises that may reject with `ClientResponseError`. Proper error handling improves user experience and simplifies debugging.
|
|
|
|
**Incorrect (ignoring or poorly handling errors):**
|
|
|
|
```javascript
|
|
// No error handling
|
|
const posts = await pb.collection('posts').getList();
|
|
|
|
// Generic catch that loses information
|
|
try {
|
|
await pb.collection('posts').create({ title: '' });
|
|
} catch (e) {
|
|
alert('Something went wrong'); // No useful info
|
|
}
|
|
|
|
// Not checking specific error types
|
|
try {
|
|
await pb.collection('posts').getOne('nonexistent');
|
|
} catch (e) {
|
|
console.log(e.message); // Missing status, response details
|
|
}
|
|
```
|
|
|
|
**Correct (comprehensive error handling):**
|
|
|
|
```javascript
|
|
import PocketBase, { ClientResponseError } from 'pocketbase';
|
|
|
|
const pb = new PocketBase('http://127.0.0.1:8090');
|
|
|
|
// Basic error handling with ClientResponseError
|
|
async function createPost(data) {
|
|
try {
|
|
return await pb.collection('posts').create(data);
|
|
} catch (error) {
|
|
if (error instanceof ClientResponseError) {
|
|
console.log('Status:', error.status);
|
|
console.log('Response:', error.response);
|
|
console.log('URL:', error.url);
|
|
console.log('Is abort:', error.isAbort);
|
|
|
|
// Handle specific status codes
|
|
switch (error.status) {
|
|
case 400:
|
|
// Validation error - extract user-friendly messages only
|
|
// IMPORTANT: Don't expose raw error.response.data to clients
|
|
// as it may leak internal field names and validation rules
|
|
const fieldErrors = {};
|
|
if (error.response?.data) {
|
|
for (const [field, details] of Object.entries(error.response.data)) {
|
|
fieldErrors[field] = details.message;
|
|
}
|
|
}
|
|
return { error: 'validation', fields: fieldErrors };
|
|
case 401:
|
|
// Unauthorized - need to login
|
|
return { error: 'unauthorized' };
|
|
case 403:
|
|
// Forbidden - no permission
|
|
return { error: 'forbidden' };
|
|
case 404:
|
|
// Not found
|
|
return { error: 'not_found' };
|
|
default:
|
|
return { error: 'server_error' };
|
|
}
|
|
}
|
|
throw error; // Re-throw non-PocketBase errors
|
|
}
|
|
}
|
|
|
|
// Handle validation errors with field details
|
|
async function updateProfile(userId, data) {
|
|
try {
|
|
return await pb.collection('users').update(userId, data);
|
|
} catch (error) {
|
|
if (error.status === 400 && error.response?.data) {
|
|
// Extract field-specific errors
|
|
const fieldErrors = {};
|
|
for (const [field, details] of Object.entries(error.response.data)) {
|
|
fieldErrors[field] = details.message;
|
|
}
|
|
return { success: false, errors: fieldErrors };
|
|
// { errors: { email: "Invalid email format", name: "Required field" } }
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Handle request cancellation
|
|
async function searchWithCancel(query) {
|
|
try {
|
|
return await pb.collection('posts').getList(1, 20, {
|
|
filter: pb.filter('title ~ {:query}', { query })
|
|
});
|
|
} catch (error) {
|
|
if (error.isAbort) {
|
|
// Request was cancelled (e.g., user typed again)
|
|
console.log('Search cancelled');
|
|
return null;
|
|
}
|
|
throw error;
|
|
}
|
|
}
|
|
|
|
// Wrapper function for consistent error handling
|
|
async function pbRequest(fn) {
|
|
try {
|
|
return { data: await fn(), error: null };
|
|
} catch (error) {
|
|
if (error instanceof ClientResponseError) {
|
|
return {
|
|
data: null,
|
|
error: {
|
|
status: error.status,
|
|
message: error.response?.message || 'Request failed',
|
|
data: error.response?.data || null
|
|
}
|
|
};
|
|
}
|
|
return {
|
|
data: null,
|
|
error: { status: 0, message: error.message, data: null }
|
|
};
|
|
}
|
|
}
|
|
|
|
// Usage
|
|
const { data, error } = await pbRequest(() =>
|
|
pb.collection('posts').getList(1, 20)
|
|
);
|
|
|
|
if (error) {
|
|
console.log('Failed:', error.message);
|
|
} else {
|
|
console.log('Posts:', data.items);
|
|
}
|
|
```
|
|
|
|
**ClientResponseError structure:**
|
|
|
|
```typescript
|
|
interface ClientResponseError {
|
|
url: string; // The request URL
|
|
status: number; // HTTP status code (0 if network error)
|
|
response: { // API response body
|
|
code: number;
|
|
message: string;
|
|
data: { [field: string]: { code: string; message: string } };
|
|
};
|
|
isAbort: boolean; // True if request was cancelled
|
|
cause: Error | null; // Original error (added in JS SDK v0.26.1)
|
|
}
|
|
```
|
|
|
|
Reference: [PocketBase Error Handling](https://github.com/pocketbase/js-sdk#error-handling)
|