Course contentsShow
← Node.js
Lesson 1918 of 1,98017. Performance Tuning and ProfilingFree lesson

Tracing Call Stacks in Promise-Heavy Code

You will simplify complex promise chains based on visibility in sampled stack traces.

In this lesson, you will learn how to improve the visibility of asynchronous execution by simplifying bloated promise chains that obscure your sampled stack traces.

The Problem: Promise Chains and "Anonymous" Traces

When you profile a Node.js application, V8 generates stack traces based on the execution context. In promise-heavy code, deep chains—especially those heavily reliant on anonymous arrow functions—often result in "lost" stacks. When the CPU profiler samples the stack, it sees the internal machinery of the Promise constructor rather than your business logic.

If you have a chain like a().then(b).then(c).then(d), the flame graph often collapses these into a single, impenetrable block of Promise.then overhead. This makes it impossible to see which specific stage of the pipeline is consuming the most CPU time.

Simplifying for Visibility

To make your code more "traceable" during profiling, you should move away from long, anonymous promise chains toward named, modular functions. By breaking a chain into named asynchronous functions, you give V8 a clear anchor point to report in the stack trace.

Avoid this:

const processData = () => 
  fetchData()
    .then(data => transform(data))
    .then(transformed => validate(transformed))
    .then(valid => save(valid));

Prefer this:

async function processData() {
  const data = await fetchData();
  const transformed = await transform(data);
  const valid = await validate(transformed);
  return save(valid);
}

Why this works for Profiling

When you use await with named functions, the stack trace explicitly lists the function names as they execute. In a CPU profile or flame graph, this creates distinct "stairs" rather than a single, flat, wide block of Promise.then calls. Because await pauses the current execution scope, the stack trace captures the specific frame where the program is actually waiting or computing, making it immediately obvious which function is responsible for the hot path. By flattening the chain, you reduce the depth of the promise-internals stack, allowing your actual logic to rise to the top of your profiling reports.

Key Takeaway: Replace anonymous .then() chains with named async/await functions to ensure your business logic appears clearly in sampled stack traces.