Evaluated Before the Try

Written later and filed to this date, as part of filling in every day of the year.

The subject line is modest: fix: keep the game playable when the browser blocks localStorage access entirely. The body’s second sentence is the whole commit:

a default parameter is evaluated at the call site BEFORE the function body’s try/catch.

The function is the preferences loader of a browser Pong. Its signature read loadPrefs(storage = localStorage), and its body wrapped every read in a try, so that a fresh install, private mode or a disabled store would fall back to defaults. Two docstrings promised exactly that: any storage failure, handled. The promise was true for every failure that happened inside the body.

But some browsers throw before the body exists. With cookies blocked entirely, or inside a sandboxed frame without same-origin permission, merely touching the localStorage getter raises a SecurityError. The default parameter touches it. By the time the try is reached there is nothing left to catch, because the exception left the function before it started.

Then the author traced where that lands. The main module calls loadPrefs() fourteen times at load, with no argument, to seed state. The first call killed the module. The page rendered, and every button on it was dead, permanently, with no error anywhere a player could see.

The fix moves the default one step right: the signature now reads storage = defaultPrefsStorage(), and that function touches the getter inside its own try, falling back to a single in-memory map shared by the loader and the saver. The sharing is the part a lesser fix would miss. Achievements, streaks and win tallies are read-modify-write cycles, and a fallback that handed each call a fresh empty store would let the game run while quietly losing every write to the previous one. One store for the session means the game plays normally; it just forgets at reload.

What the author understood: an invariant is only as real as the earliest point it can be violated, and a guard placed after that point is documentation. The comment left in the code says so plainly, that the resolver keeps “disabled storage can never crash the game” real instead of asserted.

What the body admits, almost in passing, is how the bug was found: “this session’s fresh-eyes sweep of never-swept axes.” No player reported a page full of dead buttons. Nobody blocks all cookies and then files a bug about a Pong game. The axis was swept because it had never been swept, and the commit lists three more findings from the same pass, queued with evidence.

The two pinning tests run under Node, which has no localStorage global at all. That is the cheapest browser that blocks everything: no browser.


← all writing