Limits of Backbone.Model.previousAttributes for Rolling Back a Failed save()
0 reputation · 26 Jun 2021, 12:13 UTC
0 reputation · 26 Jun 2021, 12:13 UTC
I want to restore a Backbone model's client-side state after model.save() fails, using the documented previousAttributes() snapshot and the model's error event. The intent is a clean revert without triggering view re-renders, so the plan is to reapply the snapshot with the silent option.
The model holds nested objects and arrays as attribute values. My understanding is that previousAttributes() returns a shallow snapshot, so nested references may already reflect the failed edits, making the revert incomplete. I am also unsure how far back the snapshot reaches: whether it reflects state before the last set or before the last successful sync, and how multiple unsaved changes interact with it.
Assume Backbone 1.x. Server-side effects of a partially processed request are out of scope; the concern is only local attribute consistency.
previousAttributes() capture state from the last set call or the last successful save, and what happens across several unsaved mutations?save() the only reliable way to roll back nested attributes?{silent: true} fully suppress change events, or are there edge cases?35275 reputation · 26 Jun 2021, 15:09 UTC
previousAttributes() captures the state immediately before the most recent set() call, not the last successful save(). Each set() overwrites the snapshot, so multiple unsaved mutations collapse to a single step back.save() is the only reliable way to restore nested objects and arrays. The built-in snapshot is shallow; nested references are shared with the current attributes.{silent: true} suppresses change and change:attribute events for that set() call. The model's internal state still updates; only event notification is skipped.In Backbone 1.x, the model keeps a single _previousAttributes object. On every model.set(attrs), the current attributes are shallow-copied into _previousAttributes before the new values are merged. Two consequences follow:
model.set({a: 1}); model.set({a: 2});, previousAttributes() returns {a: 1}, not the original state. There is no history stack, so you cannot walk back through several unsaved edits.model.get('items').push(3)) without calling set(), the snapshot is never refreshed, yet the nested array inside previousAttributes() is the same object reference, so it already reflects the mutation. The revert is silently incomplete.model.save(), take a deep copy of the attributes:var snapshot = _.cloneDeep(model.attributes);
// or, for plain JSON-safe data:
// var snapshot = JSON.parse(JSON.stringify(model.attributes));error handler, reapply the snapshot silently:model.set(snapshot, {silent: true});model.hasChanged() to report false afterward, reset the changed hash:model.changed = {};You can confirm each behavior in a console:
previousAttributes() only reflects the penultimate state.change listener, then reapply the snapshot with {silent: true} and confirm the listener never fires.One caveat: JSON.parse(JSON.stringify(...)) drops functions, Date objects become strings, and circular structures throw, so prefer _.cloneDeep when attributes contain non-JSON values.
Use comments to ask for clarification. Post a solution as an answer.
No question comments on this page.