proposal-error-stacks
proposal-error-stacks copied to clipboard
async stack traces
It would be convenient to get async stack traces, like what Chrome Devtools shows us in the sources tab.
F.e.
// main.js
function foo() {
setTimeout(function bar() {
const stack = getAsyncStackString()
console.log(stack)
}, 1000)
}
foo()
/*
Eventually logs the trace
at bar (main.js:4)
at foo (main.js:3)
(main.js:7)
*/
Or similar; not sure if I got that right but you get the idea.
Use case:
I'd like to detect the async stack trace of any fetch
call in an app (including 3rd-party modules from node_modules).
I would like to only have to monkey-patch fetch()
so that in each call I can have it get the stack trace. However this only gives non-async traces.
To achieve this currently, I would need to instrument the code which is more complicated.
Can this be solved by loading a custom Promise library that supports long stack traces? IIRC bluebird advertized this as one of its features.
The content of stack traces is up to the implementation. I'm not really clear on what you're asking for here.
Can this be solved by loading a custom Promise library that supports long stack traces? IIRC bluebird advertized this as one of its features.
@mk-pmb How do I tell fetch
to return a custom Promise that isn't the one built into the browser? Does fetch
rely on the Promise
global?
The content of stack traces is up to the implementation. I'm not really clear on what you're asking for here.
@ljharb I thought the purpose of this proposal was to make it not up to the implementation, but up to a standard.
What I'm hoping for is a function that returns a full async stack trace, not just a synchronous one in the current task.
For example, the output of the following,
const originalFetch = globalThis.fetch
globalThis.fetch = function(...args) {
const stack = new Error().stack
console.log(stack)
return originalFetch.apply(globalThis, args)
}
const sleep = t => new Promise(r => setTimeout(r, t))
async function one() {
console.log('1')
await sleep(10)
two()
}
async function two() {
console.log('2')
await sleep(10)
three()
}
async function three() {
console.log('3')
await sleep(10)
return await fetch('https://unpkg.com/[email protected]')
}
async function main() {
await one()
}
main()
is
1
2
3
Error
at globalThis.fetch (pen.js:5)
at three (pen.js:27)
but I was hoping it would be more like
1
2
3
Error
at globalThis.fetch (pen.js:5)
at three (pen.js:27)
at two (...)
at one (...)
at main (...)
Live example: https://codepen.io/trusktr/pen/b8fd92752b4671268f516ad3804869e4?editors=1010
The purpose of this proposal is to specify the structure, not the contents, which will remain implementation-defined.
Then as I understand the original request, it is out of scope of this spec.
out-of-loop-traces are expensive to bookkeep. i could envision performance-hit to programs written by someone naively using promises/await-calls at every opportunity with this feature.
we could instead standardize entry-point-traces for a few debug-use-cases where it makes sense:
-
tracking-down entry-point for http-requests. relevant bodies/nodejs could possibly hash-out standard to preserve entry-point-stack in
XMLHttpRequest.prototype.send
,fetch
, and node's[http/https/http2].request
-
tracking-down entry-point for
Promise.all
,Promise.race
. similar to 1, with standards-body being tc39. -
tracking-down entry-point where event/message was emitted. there's nothing a standards body can do here. its up to userland to include stack-trace in emitted message-payload.
standardizing 1 and 2 might seem like warts to language-purists, but they're outsized impact to debugging could justify it.
tracking-down entry-point for http-requests.
This sounds arbitrary and overly specific. Why should one special method of communication receive more attention than others, e.g. reading from standard input, or a window manager's notification received by a GUI application?
if you can do empirical-survey of what javascript-entry-points people find most-useful-to-know when debugging, i'm all for it.
i don't have insight in desktop-development. my insight in web-development however, is that http-request-entry-points is at top-of-the-list.
providing a standard-trace for http-requests would also prevent me from doing well-intentioned-but-stupid-things like:
let originalFetch = globalThis.fetch;
globalThis.fetch = function (...argList) {
console.error(new Error().stack);
return originalFetch(...argList);
};
that could end up being security risks.
The spec should have reasons that make sense independent of popular vote and fashions. We should build mechanisms that empower all developers to more easily debug whatever they're working on. In your fetch example case one solution might be to have browser vendors implement flags to enable a security-preserving version of long stack traces on parts of their API.
While I agree with the OP on this, I understand that this proposal is about form and not content. On that note, can there not be an another accessor property with the same setup as stack, but guaranteed to return a complete-with-async-frames stack trace? The trace returned by stack can remain as it is, with content decided by the vendors, but there does need to be a guaranteed approach to getting a complete stack trace that's not missing the async frames.
I have run into issues because of situations like this. For instance, in Firefox, as long as the developer tools are open, some code with functionality that probes the stack trace works as intended. However, without the developer tools, the same code breaks because they stop adding the async frames to the stack trace.
can there not be an another accessor property with the same setup as stack, but guaranteed to return a complete-with-async-frames stack trace?
I see no obstacle. Someone should make a proposal for that.
I'm not sure I understood the example though. Whether and in which circumstances a specific web browser does things one way or another, seems to be a product-specific problem.
@mk-pmb it can not be an accessor property, only a static method, so it can be shadowed per-compartment. .stack
, when specified, will be marked "legacy" so that it can be absent in spec-compliant engines.
I'd be happy even if it were as static method. Just so long as there is a consistent way to get it, that's good enough.