낙관적 갱신과 복구
Optimistic Overlay And Recovery
Show a speculative change while preserving an authoritative base for recovery.
GitHub에서 원문 보기 ↗새 탭Repository Boundary
Show a speculative change while preserving an authoritative base for recovery. This pattern belongs to Design Engineering and adds no Layout CSS or shared store implementation.
Reusable Method
Use for a bounded reversible presentation change when failure is recoverable. Prefer pending-until-confirmed behavior for irreversible or high-consequence actions.
State Model And Ownership
The cache owns base; the operation owns an overlay and ID. The displayed value is base plus overlay. This minimal example permits one edit at a time; concurrent mutations require ordered overlays or per-entity serialization.
Transitions
| Event and precondition | Next state | Effect or invariant |
|---|---|---|
| Begin mutation while idle | Overlay pending | Display prediction without rewriting base |
| Matching rejection | Overlay removed | Reveal current base, not an old whole-cache snapshot |
| Matching acknowledgement | Base becomes server value | Remove overlay atomically |
| Uncertain outcome | Reconciliation required | Do not announce rollback of the server |
Minimal Executable Example
Run this standalone JavaScript block with Node.js 22 or newer, or run all pattern examples from a repository checkout with npm run test:state-management. The assertions exercise the local model, not a browser or backend.
import assert from "node:assert/strict";
let base = { name: "Before", revision: 1 }, overlay = null, serial = 0;
const view = () => overlay ? { ...base, name: overlay.name } : base;
function begin(name) {
if (overlay) return null;
overlay = { id: ++serial, name };
return overlay.id;
}
function settle(id, accepted) {
if (overlay?.id !== id) return;
if (accepted) base = accepted;
overlay = null;
}
const a = begin("Predicted");
assert.equal(view().name, "Predicted");
base = { name: "Remote", revision: 2 }; // A newer authoritative observation.
settle(a, null); // Definitive rejection, not an ambiguous timeout.
assert.equal(view().name, "Remote");
const b = begin("Next");
settle(a, null);
assert.equal(view().name, "Next");
settle(b, { name: "Normalized", revision: 3 });
assert.equal(view().name, "Normalized");What Breaks If Removed
Restoring a whole-cache snapshot on failure can erase unrelated or newer remote changes. Without correlation, an old failure can remove a newer optimistic operation.
Composition And Substitution
Compose with single flight for this serialized model. Use ID selection for row mutations. Define version ordering before admitting concurrent acknowledgements into a shared cache.
Opinionated Guidance
Separate a definitive rejection from a lost response: a timeout may have committed remotely. Reconcile uncertain results before promising the change was undone.
Platform-Specific Guidance
Keep pending and error information available beyond color or animation. A disappearance animation must not decide whether the underlying entity exists.
Unsupported Absolutes
An inverse UI patch is not a backend undo protocol. The sample does not implement concurrent multi-writer revision arbitration.
Verification Contract
- Reject a speculative edit after a newer base arrives; preserve the newer base.
- Deliver an old failure during a new edit; retain the new overlay.
- Return a normalized server value and verify it replaces the prediction.
Record actual outcomes separately using the verification matrix.
Source, License, And Attribution
Locally authored synthesis and examples. No upstream implementation is copied. These experimental contracts describe consumer-owned behavior; review when a failure, ownership change, or platform change invalidates an assumption.
IA Navigation
Parent: State Management. Next: Continue the state management route.