Initial commit
This commit is contained in:
@@ -0,0 +1,165 @@
|
||||
---
|
||||
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)
|
||||
Reference in New Issue
Block a user