TanStack DB 提供全面的错误处理功能,以确保数据同步和状态管理的健壮性。本指南涵盖内置的错误处理机制以及如何有效地使用它们。
TanStack DB 提供命名错误类,以实现更好的错误处理和类型安全。所有错误类都可以从 @tanstack/db (或更常见的是,特定于框架的包,例如 @tanstack/react-db) 导入。
import {
SchemaValidationError,
CollectionInErrorStateError,
DuplicateKeyError,
MissingHandlerError,
TransactionError,
// ... and many more
} from "@tanstack/db"
import {
SchemaValidationError,
CollectionInErrorStateError,
DuplicateKeyError,
MissingHandlerError,
TransactionError,
// ... and many more
} from "@tanstack/db"
在插入或更新操作期间,当数据与集合的模式不匹配时会抛出此错误。
import { SchemaValidationError } from "@tanstack/db"
try {
todoCollection.insert({ text: 123 }) // Invalid type
} catch (error) {
if (error instanceof SchemaValidationError) {
console.log(error.type) // 'insert' or 'update'
console.log(error.issues) // Array of validation issues
// Example issue: { message: "Expected string, received number", path: ["text"] }
}
}
import { SchemaValidationError } from "@tanstack/db"
try {
todoCollection.insert({ text: 123 }) // Invalid type
} catch (error) {
if (error instanceof SchemaValidationError) {
console.log(error.type) // 'insert' or 'update'
console.log(error.issues) // Array of validation issues
// Example issue: { message: "Expected string, received number", path: ["text"] }
}
}
错误包含:
集合跟踪其状态并在状态之间转换。
import { useLiveQuery } from "@tanstack/react-db"
const TodoList = () => {
const { data, status, isError, isLoading, isReady } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
if (isError) {
return <div>Collection is in error state</div>
}
if (isLoading) {
return <div>Loading...</div>
}
return <div>{data?.map(todo => <div key={todo.id}>{todo.text}</div>)}</div>
}
import { useLiveQuery } from "@tanstack/react-db"
const TodoList = () => {
const { data, status, isError, isLoading, isReady } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
if (isError) {
return <div>Collection is in error state</div>
}
if (isLoading) {
return <div>Loading...</div>
}
return <div>{data?.map(todo => <div key={todo.id}>{todo.text}</div>)}</div>
}
集合状态值:
当修改失败时,TanStack DB 会自动回滚乐观更新。
const todoCollection = createCollection({
id: "todos",
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
// Throwing an error will rollback the optimistic state
throw new Error(`HTTP Error: ${response.status}`)
}
return response.json()
},
})
// Usage - optimistic update will be rolled back if the mutation fails
try {
const tx = todoCollection.insert({
id: "1",
text: "New todo",
completed: false,
})
await tx.isPersisted.promise
} catch (error) {
// The optimistic update has been automatically rolled back
console.error("Failed to create todo:", error)
}
const todoCollection = createCollection({
id: "todos",
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
// Throwing an error will rollback the optimistic state
throw new Error(`HTTP Error: ${response.status}`)
}
return response.json()
},
})
// Usage - optimistic update will be rolled back if the mutation fails
try {
const tx = todoCollection.insert({
id: "1",
text: "New todo",
completed: false,
})
await tx.isPersisted.promise
} catch (error) {
// The optimistic update has been automatically rolled back
console.error("Failed to create todo:", error)
}
事务具有以下状态:
从集合操作中访问事务错误信息。
const todoCollection = createCollection({
id: "todos",
onUpdate: async ({ transaction }) => {
const response = await fetch(`/api/todos/${transaction.mutations[0].key}`, {
method: "PUT",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`Update failed: ${response.status}`)
}
},
})
try {
const tx = await todoCollection.update("todo-1", (draft) => {
draft.completed = true
})
await tx.isPersisted.promise
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "Update failed: 500", error: Error }
}
const todoCollection = createCollection({
id: "todos",
onUpdate: async ({ transaction }) => {
const response = await fetch(`/api/todos/${transaction.mutations[0].key}`, {
method: "PUT",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`Update failed: ${response.status}`)
}
},
})
try {
const tx = await todoCollection.update("todo-1", (draft) => {
draft.completed = true
})
await tx.isPersisted.promise
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "Update failed: 500", error: Error }
}
或通过手动创建事务。
const tx = createTransaction({
mutationFn: async ({ transaction }) => {
throw new Error("API failed")
}
})
tx.mutate(() => {
collection.insert({ id: "1", text: "Item" })
})
try {
await tx.commit()
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "API failed", error: Error }
}
const tx = createTransaction({
mutationFn: async ({ transaction }) => {
throw new Error("API failed")
}
})
tx.mutate(() => {
collection.insert({ id: "1", text: "Item" })
})
try {
await tx.commit()
} catch (error) {
// Transaction has been rolled back
console.log(tx.state) // "failed"
console.log(tx.error) // { message: "API failed", error: Error }
}
处于 error 状态的集合无法执行操作,必须手动恢复。
import { CollectionInErrorStateError } from "@tanstack/db"
try {
todoCollection.insert(newTodo)
} catch (error) {
if (error instanceof CollectionInErrorStateError) {
// Collection needs to be cleaned up and restarted
await todoCollection.cleanup()
// Now retry the operation
todoCollection.insert(newTodo)
}
}
import { CollectionInErrorStateError } from "@tanstack/db"
try {
todoCollection.insert(newTodo)
} catch (error) {
if (error instanceof CollectionInErrorStateError) {
// Collection needs to be cleaned up and restarted
await todoCollection.cleanup()
// Now retry the operation
todoCollection.insert(newTodo)
}
}
直接修改需要配置处理器。
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
// Missing onInsert handler
})
// This will throw an error
todoCollection.insert(newTodo)
// Error: Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
// Missing onInsert handler
})
// This will throw an error
todoCollection.insert(newTodo)
// Error: Collection.insert called directly (not within an explicit transaction) but no 'onInsert' handler is configured
插入具有现有键的项目将抛出错误。
import { DuplicateKeyError } from "@tanstack/db"
try {
todoCollection.insert({ id: "existing-id", text: "Todo" })
} catch (error) {
if (error instanceof DuplicateKeyError) {
console.log(`Duplicate key: ${error.message}`)
}
}
import { DuplicateKeyError } from "@tanstack/db"
try {
todoCollection.insert({ id: "existing-id", text: "Todo" })
} catch (error) {
if (error instanceof DuplicateKeyError) {
console.log(`Duplicate key: ${error.message}`)
}
}
模式验证必须是同步的。
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
schema: {
"~standard": {
validate: async (data) => { // Async validation not allowed
// ...
}
}
}
})
// Will throw: Schema validation must be synchronous
const todoCollection = createCollection({
id: "todos",
getKey: (todo) => todo.id,
schema: {
"~standard": {
validate: async (data) => { // Async validation not allowed
// ...
}
}
}
})
// Will throw: Schema validation must be synchronous
查询集合会优雅地处理同步错误,即使出现错误也会将集合标记为就绪,以避免阻塞应用程序。
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`)
}
return response.json()
},
queryClient,
getKey: (item) => item.id,
schema: todoSchema,
// Standard TanStack Query error handling options
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
)
import { queryCollectionOptions } from "@tanstack/query-db-collection"
const todoCollection = createCollection(
queryCollectionOptions({
queryKey: ["todos"],
queryFn: async () => {
const response = await fetch("/api/todos")
if (!response.ok) {
throw new Error(`Failed to fetch: ${response.status}`)
}
return response.json()
},
queryClient,
getKey: (item) => item.id,
schema: todoSchema,
// Standard TanStack Query error handling options
retry: 3,
retryDelay: (attemptIndex) => Math.min(1000 * 2 ** attemptIndex, 30000),
})
)
发生同步错误时:
同步函数必须处理其自身的写入操作期间的错误。
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, write, commit }) => {
begin()
try {
// Will throw if key already exists
write({ type: "insert", value: { id: "existing-id", text: "Todo" } })
} catch (error) {
// Error: Cannot insert document with key "existing-id" from sync because it already exists
}
commit()
}
}
})
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, write, commit }) => {
begin()
try {
// Will throw if key already exists
write({ type: "insert", value: { id: "existing-id", text: "Todo" } })
} catch (error) {
// Error: Cannot insert document with key "existing-id" from sync because it already exists
}
commit()
}
}
})
清理错误是隔离的,以防止阻塞清理过程。
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, commit }) => {
begin()
commit()
// Return a cleanup function
return () => {
// If this throws, the error is re-thrown in a microtask
// but cleanup continues successfully
throw new Error("Sync cleanup failed")
}
},
},
})
// Cleanup completes even if the sync cleanup function throws
await collection.cleanup() // Resolves successfully
// Error is re-thrown asynchronously via queueMicrotask
const collection = createCollection({
id: "todos",
sync: {
sync: ({ begin, commit }) => {
begin()
commit()
// Return a cleanup function
return () => {
// If this throws, the error is re-thrown in a microtask
// but cleanup continues successfully
throw new Error("Sync cleanup failed")
}
},
},
})
// Cleanup completes even if the sync cleanup function throws
await collection.cleanup() // Resolves successfully
// Error is re-thrown asynchronously via queueMicrotask
清理处于错误状态的集合。
if (todoCollection.status === "error") {
// Cleanup will stop sync and reset the collection
await todoCollection.cleanup()
// Collection will automatically restart on next access
todoCollection.preload() // Or any other operation
}
if (todoCollection.status === "error") {
// Cleanup will stop sync and reset the collection
await todoCollection.cleanup()
// Collection will automatically restart on next access
todoCollection.preload() // Or any other operation
}
即使同步失败,集合仍可使用缓存数据继续运行。
const TodoApp = () => {
const { data, isError } = useLiveQuery((query) =>
query.from({ todos: todoCollection })
)
return (
<div>
{isError && (
<div>Sync failed, but you can still view cached data</div>
)}
{data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
</div>
)
}
const TodoApp = () => {
const { data, isError } = useLiveQuery((query) =>
query.from({ todos: todoCollection })
)
return (
<div>
{isError && (
<div>Sync failed, but you can still view cached data</div>
)}
{data?.map(todo => <TodoItem key={todo.id} todo={todo} />)}
</div>
)
}
当事务失败时,冲突的事务会自动回滚。
const tx1 = createTransaction({ mutationFn: async () => {} })
const tx2 = createTransaction({ mutationFn: async () => {} })
tx1.mutate(() => collection.update("1", draft => { draft.value = "A" }))
tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same item
// Rolling back tx1 will also rollback tx2 due to conflict
tx1.rollback() // tx2 is automatically rolled back
const tx1 = createTransaction({ mutationFn: async () => {} })
const tx2 = createTransaction({ mutationFn: async () => {} })
tx1.mutate(() => collection.update("1", draft => { draft.value = "A" }))
tx2.mutate(() => collection.update("1", draft => { draft.value = "B" })) // Same item
// Rolling back tx1 will also rollback tx2 due to conflict
tx1.rollback() // tx2 is automatically rolled back
事务在操作前验证其状态。
const tx = createTransaction({ mutationFn: async () => {} })
// Complete the transaction
await tx.commit()
// These will throw:
tx.mutate(() => {}) // Error: You can no longer call .mutate() as the transaction is no longer pending
tx.commit() // Error: You can no longer call .commit() as the transaction is no longer pending
tx.rollback() // Error: You can no longer call .rollback() as the transaction is already completed
const tx = createTransaction({ mutationFn: async () => {} })
// Complete the transaction
await tx.commit()
// These will throw:
tx.mutate(() => {}) // Error: You can no longer call .mutate() as the transaction is no longer pending
tx.commit() // Error: You can no longer call .commit() as the transaction is no longer pending
tx.rollback() // Error: You can no longer call .rollback() as the transaction is already completed
使用 instanceof 检查 - 使用 instanceof 而不是字符串匹配来进行错误处理。
// ✅ Good - type-safe error handling
if (error instanceof SchemaValidationError) {
// Handle validation error
}
// ❌ Avoid - brittle string matching
if (error.message.includes("validation failed")) {
// Handle validation error
}
// ✅ Good - type-safe error handling
if (error instanceof SchemaValidationError) {
// Handle validation error
}
// ❌ Avoid - brittle string matching
if (error.message.includes("validation failed")) {
// Handle validation error
}
导入特定的错误类型 - 仅导入您需要的错误类,以实现更好的 tree-shaking。
始终处理 SchemaValidationError - 为验证失败提供清晰的反馈。
检查集合状态 - 在 React 组件中使用 isError、isLoading、isReady 标志。
处理事务 Promise - 始终处理 isPersisted.promise 的拒绝。
import {
createCollection,
SchemaValidationError,
DuplicateKeyError,
createTransaction
} from "@tanstack/db"
import { useLiveQuery } from "@tanstack/react-db"
const todoCollection = createCollection({
id: "todos",
schema: todoSchema,
getKey: (todo) => todo.id,
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
sync: {
sync: ({ begin, write, commit }) => {
// Your sync implementation
begin()
// ... sync logic
commit()
}
}
})
const TodoApp = () => {
const { data, status, isError, isLoading } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
const handleAddTodo = async (text: string) => {
try {
const tx = await todoCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
})
// Wait for persistence
await tx.isPersisted.promise
} catch (error) {
if (error instanceof SchemaValidationError) {
alert(`Validation error: ${error.issues[0]?.message}`)
} else if (error instanceof DuplicateKeyError) {
alert("A todo with this ID already exists")
} else {
alert(`Failed to add todo: ${error.message}`)
}
}
}
const handleCleanup = async () => {
try {
await todoCollection.cleanup()
// Collection will restart on next access
} catch (error) {
console.error("Cleanup failed:", error)
}
}
if (isError) {
return (
<div>
<div>Collection error - data may be stale</div>
<button onClick={handleCleanup}>
Restart Collection
</button>
</div>
)
}
if (isLoading) {
return <div>Loading todos...</div>
}
return (
<div>
<button onClick={() => handleAddTodo("New todo")}>
Add Todo
</button>
{data?.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
import {
createCollection,
SchemaValidationError,
DuplicateKeyError,
createTransaction
} from "@tanstack/db"
import { useLiveQuery } from "@tanstack/react-db"
const todoCollection = createCollection({
id: "todos",
schema: todoSchema,
getKey: (todo) => todo.id,
onInsert: async ({ transaction }) => {
const response = await fetch("/api/todos", {
method: "POST",
body: JSON.stringify(transaction.mutations[0].modified),
})
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`)
}
return response.json()
},
sync: {
sync: ({ begin, write, commit }) => {
// Your sync implementation
begin()
// ... sync logic
commit()
}
}
})
const TodoApp = () => {
const { data, status, isError, isLoading } = useLiveQuery(
(query) => query.from({ todos: todoCollection })
)
const handleAddTodo = async (text: string) => {
try {
const tx = await todoCollection.insert({
id: crypto.randomUUID(),
text,
completed: false,
})
// Wait for persistence
await tx.isPersisted.promise
} catch (error) {
if (error instanceof SchemaValidationError) {
alert(`Validation error: ${error.issues[0]?.message}`)
} else if (error instanceof DuplicateKeyError) {
alert("A todo with this ID already exists")
} else {
alert(`Failed to add todo: ${error.message}`)
}
}
}
const handleCleanup = async () => {
try {
await todoCollection.cleanup()
// Collection will restart on next access
} catch (error) {
console.error("Cleanup failed:", error)
}
}
if (isError) {
return (
<div>
<div>Collection error - data may be stale</div>
<button onClick={handleCleanup}>
Restart Collection
</button>
</div>
)
}
if (isLoading) {
return <div>Loading todos...</div>
}
return (
<div>
<button onClick={() => handleAddTodo("New todo")}>
Add Todo
</button>
{data?.map(todo => (
<div key={todo.id}>{todo.text}</div>
))}
</div>
)
}
您的每周 JavaScript 资讯。每周一免费发送给超过 10 万开发者。