diff options
Diffstat (limited to 'src/lib/async/revertible.ts')
-rw-r--r-- | src/lib/async/revertible.ts | 52 |
1 files changed, 52 insertions, 0 deletions
diff --git a/src/lib/async/revertible.ts b/src/lib/async/revertible.ts new file mode 100644 index 000000000..3c8e3e8f9 --- /dev/null +++ b/src/lib/async/revertible.ts @@ -0,0 +1,52 @@ +import {runInAction} from 'mobx' +import {deepObserve} from 'mobx-utils' +import set from 'lodash.set' + +const ongoingActions = new Set<any>() + +export const updateDataOptimistically = async < + T extends Record<string, any>, + U, +>( + model: T, + preUpdate: () => void, + serverUpdate: () => Promise<U>, + postUpdate?: (res: U) => void, +): Promise<void> => { + if (ongoingActions.has(model)) { + return + } + ongoingActions.add(model) + + const prevState: Map<string, any> = new Map<string, any>() + const dispose = deepObserve(model, (change, path) => { + if (change.observableKind === 'object') { + if (change.type === 'update') { + prevState.set( + [path, change.name].filter(Boolean).join('.'), + change.oldValue, + ) + } else if (change.type === 'add') { + prevState.set([path, change.name].filter(Boolean).join('.'), undefined) + } + } + }) + preUpdate() + dispose() + + try { + const res = await serverUpdate() + runInAction(() => { + postUpdate?.(res) + }) + } catch (error) { + runInAction(() => { + prevState.forEach((value, path) => { + set(model, path, value) + }) + }) + throw error + } finally { + ongoingActions.delete(model) + } +} |