console.trace() looks nice, except it doesn't work for async functions (i.e., functions that call each other through an event loop), which is how many function calls happen to be coded in practice.
It doesn’t work for callback-based async (including native Promises, but some libraries like Bluebird can help). But it does work for suspending async functions (native async/await, generators)! This also applies to Error#stack and debugger call stacks.
I’m actively moving some projects I inherited from Promise APIs to async/await for this reason.
Native async/await functions use generator functions under the hood, which are an implementation of coroutines. Like all fully-fledged coroutine implementations, generators can suspend execution while preserving the stack by yielding control (hence the `yield` keyword). This requires interpreter (e.g. V8) support.
Desuguared Promise.then can't suspend the stack like that as a normal JS thread must run to completion and cannot yield to the scheduler; instead every call to `.then` pushes the new closure onto the event/microtask queue.
The difference is that an async function suspends on await, and continues to be active in the call stack until it returns. It doesn’t preserve the stack so much as continues to be on the stack. A Promise-returning function returns the Promise value synchronously and then/catch/etc continue from a new stack where they’re resolved.
it'd be nice if there's an async-trace feature, where you get a (ascii?) visualization of the graph of async calls, if given a promise.
It'd have to be able to reach deep into the core of the vm to find out where the promise was created (which might be lost if that info wasn't recorded i suppose), so may not be possible.
I’m actively moving some projects I inherited from Promise APIs to async/await for this reason.