Initial commit
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
---
|
||||
title: Understand and Control Auto-Cancellation
|
||||
impact: MEDIUM
|
||||
impactDescription: Prevents race conditions, improves UX for search/typeahead
|
||||
tags: sdk, cancellation, requests, performance
|
||||
---
|
||||
|
||||
## Understand and Control Auto-Cancellation
|
||||
|
||||
The SDK automatically cancels duplicate pending requests. This prevents race conditions but requires understanding for proper use in concurrent scenarios.
|
||||
|
||||
**Incorrect (confused by auto-cancellation):**
|
||||
|
||||
```javascript
|
||||
// These requests will interfere with each other!
|
||||
async function loadDashboard() {
|
||||
// Only the last one executes, others cancelled
|
||||
const posts = pb.collection('posts').getList(1, 20);
|
||||
const users = pb.collection('posts').getList(1, 10); // Different params but same path
|
||||
const comments = pb.collection('posts').getList(1, 5);
|
||||
|
||||
// posts and users are cancelled, only comments executes
|
||||
return Promise.all([posts, users, comments]); // First two fail!
|
||||
}
|
||||
|
||||
// Realtime combined with polling causes cancellation
|
||||
pb.collection('posts').subscribe('*', callback);
|
||||
setInterval(() => {
|
||||
pb.collection('posts').getList(); // May cancel realtime!
|
||||
}, 5000);
|
||||
```
|
||||
|
||||
**Correct (controlling auto-cancellation):**
|
||||
|
||||
```javascript
|
||||
// Disable auto-cancellation for parallel requests
|
||||
async function loadDashboard() {
|
||||
const [posts, users, recent] = await Promise.all([
|
||||
pb.collection('posts').getList(1, 20, { requestKey: null }),
|
||||
pb.collection('users').getList(1, 10, { requestKey: null }),
|
||||
pb.collection('posts').getList(1, 5, { requestKey: 'recent' })
|
||||
]);
|
||||
// All requests complete independently
|
||||
return { posts, users, recent };
|
||||
}
|
||||
|
||||
// Use unique request keys for different purposes
|
||||
async function searchPosts(query) {
|
||||
return pb.collection('posts').getList(1, 20, {
|
||||
filter: pb.filter('title ~ {:q}', { q: query }),
|
||||
requestKey: 'post-search' // Cancels previous searches only
|
||||
});
|
||||
}
|
||||
|
||||
async function loadPostDetails(postId) {
|
||||
return pb.collection('posts').getOne(postId, {
|
||||
requestKey: `post-${postId}` // Unique per post
|
||||
});
|
||||
}
|
||||
|
||||
// Typeahead search - auto-cancellation is helpful here
|
||||
async function typeaheadSearch(query) {
|
||||
// Previous search automatically cancelled when user types more
|
||||
return pb.collection('products').getList(1, 10, {
|
||||
filter: pb.filter('name ~ {:q}', { q: query })
|
||||
// No requestKey = uses default (path-based), previous cancelled
|
||||
});
|
||||
}
|
||||
|
||||
// Globally disable auto-cancellation (use carefully)
|
||||
pb.autoCancellation(false);
|
||||
|
||||
// Now all requests are independent
|
||||
await Promise.all([
|
||||
pb.collection('posts').getList(1, 20),
|
||||
pb.collection('posts').getList(1, 10),
|
||||
pb.collection('posts').getList(1, 5)
|
||||
]);
|
||||
|
||||
// Re-enable
|
||||
pb.autoCancellation(true);
|
||||
```
|
||||
|
||||
**Manual cancellation:**
|
||||
|
||||
```javascript
|
||||
// Cancel all pending requests
|
||||
pb.cancelAllRequests();
|
||||
|
||||
// Cancel specific request by key
|
||||
pb.cancelRequest('post-search');
|
||||
|
||||
// Example: Cancel on component unmount
|
||||
function PostList() {
|
||||
useEffect(() => {
|
||||
loadPosts();
|
||||
|
||||
return () => {
|
||||
// Cleanup: cancel pending requests
|
||||
pb.cancelRequest('post-list');
|
||||
};
|
||||
}, []);
|
||||
|
||||
async function loadPosts() {
|
||||
const result = await pb.collection('posts').getList(1, 20, {
|
||||
requestKey: 'post-list'
|
||||
});
|
||||
setPosts(result.items);
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cancellation in catch
|
||||
async function fetchWithCancellation() {
|
||||
try {
|
||||
return await pb.collection('posts').getList();
|
||||
} catch (error) {
|
||||
if (error.isAbort) {
|
||||
// Request was cancelled - this is expected
|
||||
console.log('Request cancelled');
|
||||
return null;
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**When to use each approach:**
|
||||
|
||||
| Scenario | Approach |
|
||||
|----------|----------|
|
||||
| Search/typeahead | Default (let it cancel) |
|
||||
| Parallel data loading | `requestKey: null` |
|
||||
| Grouped requests | Custom `requestKey` |
|
||||
| Component cleanup | `cancelRequest(key)` |
|
||||
| Testing/debugging | `autoCancellation(false)` |
|
||||
| OAuth2 flow cancel | `cancelRequest(requestKey)` — properly rejects the `authWithOAuth2()` Promise (JS SDK v0.26.8+) |
|
||||
|
||||
> **Note (JS SDK v0.26.8):** Calling `pb.cancelRequest(requestKey)` while `authWithOAuth2()` is waiting now properly rejects the returned Promise. In earlier versions the manual cancellation did not account for the waiting realtime subscription, so the Promise could hang indefinitely.
|
||||
|
||||
Reference: [PocketBase Auto-Cancellation](https://github.com/pocketbase/js-sdk#auto-cancellation)
|
||||
Reference in New Issue
Block a user