How does V8 reconstruct async stacktraces with requestAnimationFrame?

According to Zero-cost async stack traces V8 uses the Promise reference in the microtask to reconstruct an "async stacktrace". I assume similar techniques are also used in the debugger, which also shows an "async stack". However when debugging a "pseudorecursive function", like e.g. this one using requestAnimationFrame:

(function tick() { requestAnimationFrame(tick); })();

then Chrome also shows some "async stack" as shown in this question, although no Promise is present. How does V8 recover this stack? Does it collect information on how the microtask was scheduled? And if so, what prevents this from leaking memory?


It cheats basically. When the devtools is opened a debugger speaking the chrome devtools protocol is connected to v8.

It in turn calls setAsyncCallStackDepth which will "stitch" the stack whenever an async call is done (you can see how in v8/src/inspector/v8-debugger.cc in the chromium repo).

It is expensive which is why you only get it in devtools.

You can think of it as:

  • Whenever an async function is called, keep track of it by taking a stack trace.
  • When an error is thrown, get its stack trace and "stitch" the two together.

Note this was done in userland libraries before (like bluebird) and is quite useful but not great to rely on in production apps since it only works with a debugger connected.