Reading a JavaScript Stack Trace in the Browser Console
JavaScript stack traces read top to bottom instead of bottom to top, and the browser console gives you more than just the error message.
On a side project a while back, I had a checkout form that threw a cryptic TypeError only when a user clicked submit twice quickly, and my first instinct was to read the error message and ignore the wall of text underneath it. That wall of text turned out to be the stack trace, and once I actually read it in order, it pointed straight at a double-submit race condition instead of the input validation I had been blaming.
A JavaScript stack trace in the browser console reads in the opposite order from a Python traceback: the first line is where the error actually happened, and each line below it is the function that called the one above, working outward toward whatever triggered the chain -- a click handler, a promise, a script tag. That is the reverse of Python's bottom-up convention, so if you are used to reading tracebacks from the last line up, start at the top of a JavaScript trace instead.
Each line typically shows a function name and a file:line:column reference, and in Chrome, Firefox, and Edge that reference is clickable -- it jumps straight to the Sources panel at that exact line, even if the code was bundled or minified, as long as a source map is present. That last part matters: without source maps, a production stack trace will point you at line 1 of a minified bundle, which is nearly useless, so shipping source maps, even privately rather than publicly, is worth doing for exactly this reason.
Promises and async/await complicate the picture because the call stack that threw an error is not necessarily the call stack that started the operation. Modern browser consoles append an async section to the trace showing where the async function was originally invoked, which is what let me trace my double-submit bug back to the click handler that fired the second request instead of stopping at the fetch call where the error technically surfaced. Expanding that async portion, rather than stopping at the first frame, is usually where the real cause is hiding.
