Storage is a dependency, not a guarantee
The capstone intentionally keeps its saved books in memory. Rerunning or reloading starts fresh. To add persistence, inject a storage object with getItem and setItem rather than calling localStorage throughout the model. This creates one place to choose failure behavior.
On a normal origin, localStorage stores strings synchronously. Reads or writes may throw because of browser policy, quota, or unavailable storage. Parsed saved text may be invalid JSON or an obsolete schema. Validate restored data before putting it into state. An inaccessible store is different from a missing key, even if both lead to an empty initial list; communicate when saving is unavailable.
The isolated webpage labs do not have same-origin storage privileges. Use the controlled adapter below to practise. The downloadable HTTP-served project is a suitable place to add an actual localStorage adapter. Do not weaken the lab sandbox to make storage work.
Decide whether a failed write should undo the user’s in-memory action. For a reading queue, keeping the action and showing “not saved across reloads” is reasonable. For a payment workflow it would be an entirely different contract. A fallback must be visible; silent persistence failure creates false confidence.
Predict and trace
Write the output before running. Follow the values through each call; if your prediction differs, identify the first step that surprised you.
function writeSaved(storage, books) {
try {
storage.setItem("reading-queue:v1", JSON.stringify(books));
return true;
} catch {
return false;
}
}
const memory = new Map();
const storage = {
setItem(key, text) {
memory.set(key, text);
}
};
console.log(writeSaved(storage, [{ id: "js", title: "Notes" }]));
console.log(JSON.parse(memory.get("reading-queue:v1"))[0].id);
console.log(
writeSaved(
{
setItem() {
throw new Error("Blocked");
}
},
[]
)
);Check your prediction
true
js
falseThe Map is a test adapter, not persistence across program runs. A false result should drive visible feedback at the UI boundary.
Distinguish missing storage from malformed storage
A storage adapter crosses two boundaries: the storage operation can fail, and retrieved text can fail parsing or validation. A missing key is a normal empty state, while malformed text is a recovery decision. Neither should crash unrelated interaction unless the application’s policy explicitly requires it.
Version the storage key when its shape changes. Validate parsed records before exposing them as trusted application data. A JSON array alone is not enough evidence that it contains valid saved books.
function read(storage) {
const text = storage.getItem("queue:v1");
if (text === null) {
return [];
}
const value = JSON.parse(text);
if (!Array.isArray(value)) {
throw new Error("Expected list");
}
return value;
}
console.log(
read({
getItem() {
return null;
}
}).length
);
console.log(
read({
getItem() {
return "[]";
}
}).length
);Check your prediction
0
0 Both cases produce an empty array here, but they arrived through different paths. A later migration policy may need to distinguish absent data from existing serialized data.
function write(storage, value) {
try {
storage.setItem("queue:v1", JSON.stringify(value));
return true;
} catch {
return false;
}
}
const storage = {
setItem() {
throw new Error("Quota exceeded");
}
};
console.log(write(storage, [{ id: "a" }]));Check your prediction
false A storage write can fail even when serialization succeeds. The boolean gives the caller a recovery choice. It should not be converted into a claim that persistence succeeded.
Try the rule on new inputs
Test missing key, valid empty list, malformed JSON, wrong-shaped JSON and blocked writes. Decide which cases recover to empty and which show a warning. Keep the currently displayed saved state intact when a write fails.
Build it yourself
Storage Adapters: practise
Write readList(storage), returning [] for a missing key but throwing for invalid JSON or a non-array body. Use getItem(“books”). Print the missing length and catch a wrong-shape error.
// getItem returns null when the key is missing.Hint 1: the idea
Missing data is not the same as malformed saved data.
Hint 2: a first step
Parse only after checking for null, then check Array.isArray.
Show one solution
function readList(storage) {
const text = storage.getItem("books");
if (text === null) {
return [];
}
const value = JSON.parse(text);
if (!Array.isArray(value)) {
throw new Error("Invalid saved list");
}
return value;
}
console.log(
readList({
getItem() {
return null;
}
}).length
);
try {
readList({
getItem() {
return "42";
}
});
} catch (error) {
console.log(error.message);
}0
Invalid saved listThis helper checks only the outer array. Connect the book decoder before accepting its records. Test a storage read that throws and decide how the caller should tell the user.
Test a changed requirement
This helper checks only the outer array. Connect the book decoder before accepting its records. Test a storage read that throws and decide how the caller should tell the user.
Explain before moving on
What does a versioned key help you change later? Why is a Map double not proof that localStorage is available?
Completion is saved in this browser only. It does not measure mastery.