Stack trace in JavaScript: reading the call stack of an error

An error's stack trace says how the program got there before breaking: how to read it, what asynchronous code erases, and how to keep it useful.
3 min read
Believemy logo

An error message says what broke. The stack trace says how you got there, and that is nearly always the piece missing before a fix is possible.

Provided you read it the right way round, and know what it leaves out.


Definition

The stack trace is the text carried by the stack property of an Error object. It lists the functions passed through, most recent first, with file, line and column.

JAVASCRIPT
function level3() { throw new Error("nothing works"); }
function level2() { level3(); }
function level1() { level2(); }

try {
  level1();
} catch (err) {
  console.log(err.stack);
}

// Error: nothing works
//     at level3 (index.js:1:27)
//     at level2 (index.js:2:21)
//     at level1 (index.js:3:21)

The first line repeats the name and the message, the following ones read top to bottom like a walk backward. The top line is where the break happened, the lower ones tell the sequence that led to it.


What it actually tells you

  • The trace is captured at creation. It describes where the new Error happened, not the throw nor the catch. Creating the error far from the incident destroys the information.
  • It stops at a fixed depth. Under Node.js and in browsers built on the same engine, Error.stackTraceLimit is ten by default and can be raised during development.
  • Its format is not standardized. It varies between engines: useful to read and to log, never to be parsed as structured data.


What asynchronous code erases

An error thrown inside a timer or request callback leaves from an almost empty stack: the calling code handed control back long ago and no longer appears.

JAVASCRIPT
function throwLater() {
  setTimeout(() => { throw new Error("inside the timer"); }, 0);
}

throwLater();
// The trace does not mention throwLater: it already left the stack

Two reflexes limit the damage. Prefer await over callbacks, since recent engines rebuild the trace across the waits. And enrich the error where you catch it, rethrowing with the cause property, which keeps the original error and its own trace.

Good to know

On minified code, the trace points at one enormous line and one-letter names. The mapping file produced at build time, once handed to the error tracking tool, translates the trace back to the original files. Without it, a production trace is unreadable.


Frequently asked questions

Question

How do you strip internal lines from a trace?

With Error.captureStackTrace(this, MyClass) inside the constructor of a custom error: the trace then starts at the caller, without the factory's own lines. The function belongs to the V8 engine, so to Node.js and Chrome, and should be guarded by a presence test in shared code.


Question

Should the trace be shown to the user?

No. It exposes file paths and internal details of no value whatsoever to the person using the product. The screen gets an understandable message, the logs get the full trace.


Question

Why does my trace hold only one line?

Often because the error was built somewhere other than where the incident happened, for instance from a string rethrown inside a catch. Rethrow the original object, or create the new error with the old one as its cause so both traces survive.

Related terms

Discover our javaScript glossary

Every word of JavaScript explained simply: keywords, built-in objects, methods, errors and concepts. Clear definitions and examples that actually run, to learn and to troubleshoot.

Share this article

Want to help us? Share this article on your networks or even better: on your site, in an article or in your newsletter.