Object.assign throws TypeError, yet some target properties have already changed. It does not roll back successful writes when a later step fails. State and configuration updates need explicit decisions about source reads, target writes and publication.
1. Failure does not imply an unchanged target
const target = { first: 0 };
Object.defineProperty(target, 'locked', { value: 0, writable: false });
try {
Object.assign(target, { first: 1, locked: 2, last: 3 });
} catch (error) {
console.log(error instanceof TypeError); // true
}
console.log(target.first); // 1
console.log(target.locked); // 0
console.log(Object.hasOwn(target, 'last')); // false
first changes before the write to locked fails, while last is never reached. A catch that merely logs and continues can leave partially updated application state.
2. Target writes can invoke setters
const writes = [];
const target = {
set mode(value) { writes.push(value); }
};
Object.assign(target, { mode: 'dark' });
console.log(writes.join(',')); // dark
const copy = { ...target, mode: 'light' };
console.log(writes.join(',')); // dark
console.log(copy.mode); // light
assign writes through the target's setter. Object spread creates data properties on a new object without using that setter to write light. Accessors make the operations observably different.
3. Source reads can invoke getters
let reads = 0;
const source = {
get count() { reads += 1; return 7; }
};
const assigned = Object.assign({}, source);
const spread = { ...source };
console.log(reads); // 2
console.log(assigned.count); // 7
console.log(typeof Object.getOwnPropertyDescriptor(assigned, 'count').get); // undefined
console.log(spread.count); // 7
Both operations execute the getter here. The assigned result holds the retrieved value rather than the source getter. Descriptor preservation or instance behavior requires a different copying contract. Getters can throw or cause side effects, so switching to spread does not make copying infallible or pure.
4. Construct a candidate before replacing state
let state = { mode: 'light' };
const previous = state;
let reads = 0;
const patch = {
get mode() { reads += 1; throw new Error('read failed'); }
};
try {
state = { ...state, ...patch };
} catch (error) {
console.log(error.message); // read failed
}
console.log(state === previous); // true
console.log(state.mode); // light
console.log(reads); // 1
The right-hand expression fails before assignment, preserving the old state reference in this example. Construct a candidate, validate its schema and publish it through the framework's intended update mechanism.
This is not a general rollback transaction: the getter has already run. Nested objects remain shallowly shared, and external side effects are not undone. Target ordinary application data deliberately rather than applying this mechanically to class instances or behavior-rich objects.
5. Inspect state after failures
- Identify successful writes before an assign failure.
- Check getter and setter invocation.
- Distinguish values from descriptors.
- Verify old-state identity when candidate construction fails.
- Validate before publishing a new state.
All eight bilingual code blocks ran with Node.js, alongside partial-write and descriptor checks. Framework integration and performance were not tested. References: MDN Object.assign and MDN Spread syntax.




No comments yet. Be the first to share your thoughts.