处理同步错误 — React Native SDK
在此页面上
当您在Realm应用中使用Atlas Device Sync时,您可能会遇到一类新的错误: 同步错误。
Realm React Native SDK 可以帮助您检测和处理同步错误。 例如,您可以编写自己的同步错误处理程序来响应特定错误。 您还可以定义客户端应用程序如何处理客户端重置。
同步错误处理程序
您应为使用 Atlas Device Sync 的应用程序设置错误处理程序。 通用错误处理程序将检测并响应失败的同步相关 API 调用。
添加通用同步错误处理程序
通用同步错误处理程序是跟踪同步错误的好方法。 使用FlexibleSyncConfiguration ,您可以定义错误处理行为。
要添加通用同步错误处理程序,请执行以下操作:
编写错误处理函数。
为您的
RealmProvider
创建一个FlexibleSyncConfiguration
对象。将错误处理程序传递给
FlexibleSyncConfiguration
对象的onError
属性。
const syncConfigWithErrorHandling = { flexible: true, onError: (_session, error) => { console.log(error); }, }; function RealmWithErrorHandling() { return ( <RealmProvider sync={syncConfigWithErrorHandling}> <RestOfApp /> </RealmProvider> ); }
处理补偿写入错误
您可能希望同步错误处理程序以对您的应用有意义的方式专门解决补偿写入错误。 CompensatingWriteError类可以帮助您识别自定义错误处理程序中的写入错误并对其做出React 。
要处理补偿写入错误,请执行以下操作:
编写一个错误处理程序函数,使用
CompensatingWriteError
来识别补偿写入错误。为您的
RealmProvider
创建一个FlexibleSyncConfiguration
对象。将错误处理程序传递给
FlexibleSyncConfiguration
对象的onError
属性。
export const CompensatingWriteErrorHandling = () => { const [error, setError] = useState<CompensatingWriteError | undefined>( undefined, ); // Create a callback for sync error handling using CompensatingWriteError const errorCallback: ErrorCallback = (_session, error) => { if (error instanceof CompensatingWriteError) { // Handle the compensating write error as needed console.debug({ code: error.code, name: error.name, category: error.category, message: error.message, url: error.logUrl, writes: error.writes, }); setError(error); } }; return ( <AppProvider id={APP_ID}> <UserProvider fallback={LogIn}> <RealmProvider schema={[Person, Turtle]} sync={{ flexible: true, onError: errorCallback, }}> <CompensatingWriteErrorHandler error={error} /> </RealmProvider> </UserProvider> </AppProvider> ); };
处理客户端重置错误
客户端重置错误是一种同步错误,其中客户端域无法与 Atlas App Services 后端同步数据。处于此状态的客户端可以继续运行并在本地保存数据,但在执行客户端重置之前无法发送或接收同步变更集。
要学习;了解客户端重置的原因和处理模式,请参阅App Services文档中的Device Sync客户端重置。
客户端重置模式
您可以指定应用程序应使用哪种客户端重置模式将域恢复到可同步状态:
恢复未同步更改模式:选择此模式时,客户端会尝试恢复未同步更改。 如果您不想丢弃未同步的更改,请选择此模式。
恢复或丢弃未同步的更改模式:客户端首先尝试恢复尚未同步的更改。 如果客户端无法恢复未同步的数据,则无法丢弃未同步的更改,但会继续自动执行客户端重置。 当您想要启用客户端自动恢复以丢弃未同步的更改时,请选择此模式。
放弃未同步的更改模式:通过放弃自上次同步以来所做的更改,将域恢复到可同步状态。
手动恢复模式:下载 Realm 的新副本,并将不同步的 Realm 移动到备份。 将未同步的数据从 Realm 的备份副本迁移到新的可同步副本。
自动与手动客户端重置
Realm SDK 提供了客户端重置模式,可自动处理大多数客户端重置错误。
客户端自动重置模式将本地 Realm 文件恢复到可同步状态,而无需关闭 Realm 或丢失通知。 以下客户端重置模式支持客户端自动重置:
恢复未同步变更模式
恢复或丢弃未同步的变更模式
丢弃未同步更改模式
这些模式之间的差异取决于它们如何处理设备上尚未同步到后端的更改。 仅手动恢复模式不执行自动客户端重置。
选择恢复未同步更改模式以自动处理大多数客户端重置情况。 这会尝试在客户端重置时恢复未同步的更改。
如果您的应用需要无法自动处理的特定客户端重置逻辑,您可能希望或需要将手动客户端重置处理程序添加到自动客户端重置模式。
客户端重置与恢复
realm@10.23.0
版本中的新增功能。
客户端恢复是配置 Device Sync 时默认启用的功能。 启用客户端恢复后,Realm 在大多数情况下会自动托管客户端重置过程。当没有模式更改或非中断性模式更改时,客户端可以恢复未同步的更改。
要使用客户端恢复,请使用以下客户端重置模式之一配置您的域:
恢复未同步变更模式
恢复或丢弃未同步的更改
启用客户端恢复后,这些规则将决定如何集成对象,包括当后端和客户端都对同一对象进行更改时如何解决冲突:
同步在客户端重置之前未同步的本地创建的对象。
如果一个对象在服务器上被删除,但在恢复的客户端上被修改,则删除优先,客户端丢弃更新。
如果在正在恢复的客户端上删除了对象,而不是在服务器上删除了对象,则客户端将应用服务器的删除指令。
如果对同一字段的更新发生冲突,则应用客户端更新。
有关配置客户端恢复的更多信息,请参阅 Atlas App Services文档中的 客户端恢复 。
当您的应用程序进行中断性模式更改时,客户端恢复无法成功。 重大更改是您可以在服务器端模式中进行的需要执行额外操作来处理的更改。 在这种情况下,客户端重置回退到手动错误客户端重置回退。
有关中断性与非中断性模式更改的信息,请参阅 Atlas App Services文档中的 中断性与非中断性更改快速参考 。
恢复未同步更改模式
当您选择恢复未同步更改模式时,客户端会尝试使用客户端恢复来恢复未同步更改。 如果您不想丢弃未同步的更改,请选择此模式。
要使用恢复未同步更改模式处理客户端重置,请将ClientResetConfiguration传递给FlexibleSyncConfiguration的clientReset
字段。在ClientResetConfiguration
中包含这些属性:
mode
:设置为"recoverUnsyncedChanges"
。onBefore
:可选。 当 SDK 从后端收到客户端重置错误时,在 SDK 执行此模式之前调用的回调函数。 提供 Realm 的副本。onAfter
:可选。 SDK 成功执行此模式后调用的回调函数。 提供客户端重置前后 Realm 的实例。onFallback
:可选。 仅当自动恢复失败时 SDK 才会调用的回调函数。 有关详细信息,请参阅“手动客户端重置回退”部分。
以下示例实现了恢复未同步更改模式:
const syncConfigWithRecoverClientReset = { flexible: true, clientReset: { mode: 'recoverUnsyncedChanges', onBefore: realm => { // This block could be used for custom recovery, reporting, debugging etc. }, onAfter: (beforeRealm, afterRealm) => { // This block could be used for custom recovery, reporting, debugging etc. }, onFallback: (session, path) => { // See below "Manual Client Reset Fallback" section for example }, }, }; function RealmWithRecoverUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithRecoverClientReset}> <RestOfApp /> </RealmProvider> ); }
const syncConfigWithRecoverClientReset = { flexible: true, clientReset: { mode: Realm.ClientResetMode.RecoverUnsyncedChanges, onBefore: realm => { // This block could be used for custom recovery, reporting, debugging etc. }, onAfter: (beforeRealm, afterRealm) => { // This block could be used for custom recovery, reporting, debugging etc. }, onFallback: (session, path) => { // See below "Manual Client Reset Fallback" section for example }, }, }; function RealmWithRecoverUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithRecoverClientReset}> <RestOfApp /> </RealmProvider> ); }
恢复或放弃未同步更改模式
在恢复或丢弃未同步更改模式下,客户端首先尝试恢复尚未同步的更改。 如果客户端无法恢复未同步的数据,则会放弃未同步的更改,但会继续自动执行客户端重置。 当您想要启用客户端自动恢复以丢弃未同步的更改时,请选择此模式。
如果您的应用程序不能丢失尚未同步到后端的本地数据,则不要使用恢复或丢弃未同步的更改模式。
要使用恢复或丢弃未同步更改模式处理客户端重置,请将ClientResetConfiguration传递给FlexibleSyncConfiguration的clientReset
字段。在ClientResetConfiguration
中包含这些属性:
mode
:设置为"recoverOrDiscardUnsyncedChanges"
。onBefore
:可选。 当 SDK 从后端收到客户端重置错误时,在 SDK 执行此模式之前调用的回调函数。 提供 Realm 的副本。onAfter
:可选。 SDK 成功执行此模式后调用的回调函数。 提供客户端重置前后 Realm 的实例。onFallback()
:可选。 仅当自动恢复和丢弃更改均失败时,SDK 才会调用的回调函数。 有关详细信息,请参阅“手动客户端重置回退”部分。
以下示例实现了恢复未同步更改模式:
const syncConfigWithRecoverDiscardClientReset = { flexible: true, clientReset: { mode: 'recoverOrDiscardUnsyncedChanges', onBefore: realm => { // This block could be used for custom recovery, reporting, debugging etc. }, onAfter: (beforeRealm, afterRealm) => { // This block could be used for custom recovery, reporting, debugging etc. }, onFallback: (session, path) => { // See below "Manual Client Reset Fallback" section for example }, }, }; function RealmWithRecoverOrDiscardUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithRecoverDiscardClientReset}> <RestOfApp /> </RealmProvider> ); }
const syncConfigWithRecoverDiscardClientReset = { flexible: true, clientReset: { mode: Realm.ClientResetMode.RecoverOrDiscardUnsyncedChanges, onBefore: realm => { // This block could be used for custom recovery, reporting, debugging etc. }, onAfter: (beforeRealm, afterRealm) => { // This block could be used for custom recovery, reporting, debugging etc. }, onFallback: (session, path) => { // See below "Manual Client Reset Fallback" section for example }, }, }; function RealmWithRecoverOrDiscardUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithRecoverDiscardClientReset}> <RestOfApp /> </RealmProvider> ); }
手动客户端重置回退
如果带恢复的客户端重置无法自动完成(例如发生中断性模式更改时),则客户端重置过程将由手动错误处理程序完成。 这可能发生在使用恢复模式的客户端重置、恢复未同步更改以及恢复或丢弃未同步更改的客户端中。
您必须在SyncConfiguration.onFallback()
回调中提供手动客户端重置实施。 onFallback()
接受两个参数:
session
:表示Device Sync会话状态的会话对象。path
:包含当前 Realm 文件路径的字符串。
以下示例演示了如何通过丢弃所有未同步的更改来手动处理此错误情况:
let realm; // value assigned in <RestOfApp> with useRealm() const syncConfigWithClientResetFallback = { flexible: true, clientReset: { mode: 'recoverOrDiscardUnsyncedChanges', // or "recoverUnsyncedChanges" // can also include `onBefore` and `onAfter` callbacks onFallback: (_session, path) => { try { // Prompt user to perform a client reset immediately. If they don't, // they won't receive any data from the server until they restart the app // and all changes they make will be discarded when the app restarts. const didUserConfirmReset = showUserAConfirmationDialog(); if (didUserConfirmReset) { // Close and delete old realm from device realm.close(); Realm.deleteFile(path); // Perform client reset Realm.App.Sync.initiateClientReset(app, path); // Navigate the user back to the main page or reopen the // the Realm and reinitialize the current page } } catch (err) { // Reset failed. Notify user that they'll need to // update the app } }, }, }; function RealmWithManualClientResetFallback() { return ( <RealmProvider sync={syncConfigWithClientResetFallback}> <RestOfApp /> </RealmProvider> ); } function RestOfApp() { // Assigning variable defined above to a realm. realm = useRealm(); return <>{/* Other components in rest of app */}</>; }
let realm; // value assigned in <RestOfApp> with useRealm() const syncConfigWithClientResetFallback = { flexible: true, clientReset: { mode: Realm.ClientResetMode.RecoverOrDiscardUnsyncedChanges, // or "recoverUnsyncedChanges" // can also include `onBefore` and `onAfter` callbacks onFallback: (_session, path) => { try { // Prompt user to perform a client reset immediately. If they don't, // they won't receive any data from the server until they restart the app // and all changes they make will be discarded when the app restarts. const didUserConfirmReset = showUserAConfirmationDialog(); if (didUserConfirmReset) { // Close and delete old realm from device realm.close(); Realm.deleteFile(path); // Perform client reset Realm.App.Sync.initiateClientReset(app, path); // Navigate the user back to the main page or reopen the // the Realm and reinitialize the current page } } catch (err) { // Reset failed. Notify user that they'll need to // update the app } }, }, }; function RealmWithManualClientResetFallback() { return ( <RealmProvider sync={syncConfigWithClientResetFallback}> <RestOfApp /> </RealmProvider> ); } function RestOfApp() { // Assigning variable defined above to a realm. realm = useRealm(); return <>{/* Other components in rest of app */}</>; }
丢弃未同步更改模式
realm@10.11.0
版本中的新增功能。
在版本realm@10.23.0
中进行了更改:模式从“discardLocal”重命名为“discardUnsyncedChanges”。 两者目前都可以使用,但在未来的版本中,“discardLocal”将被删除。 “clientResetBefore”和“clientResetAfter”回调分别重命名为“onBefore”和“onAfter”。
放弃未同步的更改模式会永久删除自上次成功同步以来所做的所有本地未同步更改。 当您的应用需要与自动 客户端恢复一致的客户端恢复逻辑,或者您不想恢复未同步的数据时,可以使用此模式。
如果您的应用程序不能丢失尚未同步到后端的本地数据,请勿使用丢弃未同步的更改模式。
要使用丢弃未同步更改模式处理客户端重置,请将ClientResetConfiguration传递给FlexibleSyncConfiguration的clientReset
字段。在ClientResetConfiguration
中包含这些属性:
mode
:设置为"discardUnsyncedChanges"
。onBefore
:可选。 当 SDK 从后端收到客户端重置错误时,在 SDK 执行此模式之前调用的回调函数。 提供 Realm 的副本。onAfter
:可选。 SDK 成功执行此模式后调用的回调函数。 提供客户端重置前后 Realm 的实例。
以下示例实现了丢弃未同步更改模式:
const syncConfigWithDiscardClientReset = { flexible: true, clientReset: { mode: 'discardUnsyncedChanges', onBefore: realm => { console.log('Beginning client reset for ', realm.path); }, onAfter: (beforeRealm, afterRealm) => { console.log('Finished client reset for', beforeRealm.path); console.log('New realm path', afterRealm.path); }, }, }; function RealmWitDiscardUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithDiscardClientReset}> <RestOfApp /> </RealmProvider> ); }
const syncConfigWithDiscardClientReset = { flexible: true, clientReset: { mode: Realm.ClientResetMode.DiscardUnsyncedChanges, onBefore: realm => { console.log('Beginning client reset for ', realm.path); }, onAfter: (beforeRealm, afterRealm) => { console.log('Finished client reset for', beforeRealm.path); console.log('New realm path', afterRealm.path); }, }, }; function RealmWitDiscardUnsyncedChangesClientReset() { return ( <RealmProvider sync={syncConfigWithDiscardClientReset}> <RestOfApp /> </RealmProvider> ); }
在中断性模式更改后丢弃未同步的更改
如果您的应用程序遇到中断性模式更改,则丢弃未同步的更改模式无法自动处理由此导致的客户端重置。 相反,您必须在 SyncConfiguration error()
回调中提供手动客户端重置实施。 以下示例演示了如何通过丢弃所有未同步的更改来手动处理此错误情况:
// Once you have opened your Realm, you will have to keep a reference to it. // In the error handler, this reference is called `realm` async function handleSyncError(session, syncError) { if (syncError.name == 'ClientReset') { console.log(syncError); try { console.log('error type is ClientReset....'); const path = realm.path; // realm.path will not be accessible after realm.close() realm.close(); Realm.App.Sync.initiateClientReset(app, path); // Download Realm from the server. // Ensure that the backend state is fully downloaded before proceeding, // which is the default behavior. realm = await Realm.open(config); realm.close(); } catch (err) { console.error(err); } } else { // ...handle other error types } } const syncConfigWithDiscardAfterBreakingSchemaChanges = { flexible: true, clientReset: { mode: 'discardUnsyncedChanges', onBefore: realm => { // NOT used with destructive schema changes console.log('Beginning client reset for ', realm.path); }, onAfter: (beforeRealm, afterRealm) => { // Destructive schema changes do not hit this function. // Instead, they go through the error handler. console.log('Finished client reset for', beforeRealm.path); console.log('New realm path', afterRealm.path); }, }, onError: handleSyncError, // invoked with destructive schema changes }; function RealmWitDiscardAfterBreakingSchemaChangesClientReset() { return ( <RealmProvider sync={syncConfigWithDiscardAfterBreakingSchemaChanges}> <RestOfApp /> </RealmProvider> ); }
// Once you have opened your Realm, you will have to keep a reference to it. // In the error handler, this reference is called `realm` async function handleSyncError(session, syncError) { if (syncError.name == 'ClientReset') { console.log(syncError); try { console.log('error type is ClientReset....'); const path = realm.path; // realm.path will not be accessible after realm.close() realm.close(); Realm.App.Sync.initiateClientReset(app, path); // Download Realm from the server. // Ensure that the backend state is fully downloaded before proceeding, // which is the default behavior. realm = await Realm.open(config); realm.close(); } catch (err) { console.error(err); } } else { // ...handle other error types } } const syncConfigWithDiscardAfterBreakingSchemaChanges = { flexible: true, clientReset: { mode: Realm.ClientResetMode.DiscardUnsyncedChanges, onBefore: realm => { // NOT used with destructive schema changes console.log('Beginning client reset for ', realm.path); }, onAfter: (beforeRealm, afterRealm) => { // Destructive schema changes do not hit this function. // Instead, they go through the error handler. console.log('Finished client reset for', beforeRealm.path); console.log('New realm path', afterRealm.path); }, }, onError: handleSyncError, // invoked with destructive schema changes }; function RealmWitDiscardAfterBreakingSchemaChangesClientReset() { return ( <RealmProvider sync={syncConfigWithDiscardAfterBreakingSchemaChanges}> <RestOfApp /> </RealmProvider> ); }
手动模式
在版本realm@10.23.0
中进行了更改:添加了 onManual 回调
在手动模式下,您可以定义自己的客户端重置处理程序。 如果自动恢复逻辑不适用于您的应用,并且您无法丢弃未同步的本地数据,您可能需要使用手动客户端重置处理程序。
要使用手动模式处理客户端重置,请将ClientResetConfiguration传递给FlexibleSyncConfiguration的clientReset
字段。在ClientResetConfiguration
中包含这些属性:
mode
:设置为"manual"
。onManual
:可选。 客户端重置时调用的回调函数。 提供有关同步会话和当前 Realm 路径的信息。 如果您不设置onManual
错误处理程序,则客户端重置错误将回退到常规同步错误处理程序。
const syncConfigWithManualClientReset = { flexible: true, clientReset: { mode: 'manual', onManual: (session, path) => { // handle manual client reset here }, }, }; function RealmWitManualClientReset() { return ( <RealmProvider sync={syncConfigWithManualClientReset}> <RestOfApp /> </RealmProvider> ); }
const syncConfigWithManualClientReset = { flexible: true, clientReset: { mode: 'manual', onManual: (session, path) => { // handle manual client reset here }, }, }; function RealmWitManualClientReset() { return ( <RealmProvider sync={syncConfigWithManualClientReset}> <RestOfApp /> </RealmProvider> ); }
手动数据恢复
要从手动客户端重置中恢复数据,需要大量代码、模式让步和自定义冲突解决逻辑。 如果您需要实施自己的自定义客户端重置逻辑,请参阅手动客户端重置数据恢复高级指南。
测试客户端重置处理
您可以通过终止并重新启用 Device Sync 来手动测试应用程序的客户端重置处理。
当您终止并重新启用 Sync 时,之前使用 Sync 连接的客户端在执行客户端重置之前无法进行连接。 终止同步会从服务器中删除允许客户端同步的元数据。 客户端必须从服务器下载 Realm 的新副本。 服务器向这些客户端发送客户端重置错误。 因此,当您终止同步时,就会trigger客户端重置条件。
要测试客户端重置处理,请执行以下操作:
从客户端应用程序写入数据并等待其同步。
终止并重新启用 Device Sync。
再次运行客户端应用程序。 当应用尝试连接到服务器时,应该会出现客户端重置错误。
警告
当您在客户端应用程序中迭代进行客户端重置处理时,您可能需要反复终止并重新启用 Sync。 终止并重新启用同步会导致所有现有客户端在完成客户端重置之前无法进行同步。 为了避免在生产中出现这种情况,请在开发环境中测试客户端重置处理。