-
Notifications
You must be signed in to change notification settings - Fork 394
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
stack / call history support for async sequences of promises? #108
Comments
Hey @gr2m, this is an excellent question, and there's quite a bit wrapped up in it :) The root of the problem is that asynchronous operations cross stack boundaries, i.e. the stack is most likely cleared between each of We've talked about ways to track stack jumps, and it is doable. Basically, when.js would have to generate stack traces internally at several key points, filter out when.js-specific lines, and record the result such that they could be stitched back together later into something relatively coherent and meaningful. This is something we could support in To be honest, It hasn't really been a priority for us yet, but I do appreciate that promises can be difficult to debug, and I would love to find ways to help! I wonder if @Riccieri or @Twisol would be interested in taking this on as a project? |
Oh, and ... LOL |
Thanks for the response! Would be great to have an option for that in I wonder if that's actually a common use case? Are there any typical workarounds that I could use until it's build into when.js? |
That is something I'm interested on, I've faced a few confusing stack traces myself :) I could research that! I know that there're a few existing projects for making stack traces on JS. Taking an optional dependency is an option for |
Awesome. Thanks @Riccieri! Yeah, we can pretty much do anything we need with The stack trace collection and stitching mechanisms will be the challenging part, for sure. |
I think the biggest potential issue is that each browser formats its stack traces differently. I've only seen this successfully done in Node.js, where you're always using V8, or in libraries that make engine-specific assumptions. Since there's no standard on stack traces to aim for, the best we could do is a whitelisting approach: feature-detect on stack traces and include an implementation for each variant. I dislike this, but since it's for |
@Twisol Hmmm, yes, good points. Also, Chrome (or maybe this is a v8 thing) has some new APIs for dealing with stack traces that we should investigate. If the first version of this feature only worked in v8, I would probably be ok with that since this would be for debugging only. We could look into ways to support other environments as a follow-on exercise. |
@briancavalier will do! Thanks for the support! The only naive approach I can think of is to manually do something like |
I'd also not aim to make this promise stack thing look like the original stack of the respective browser. Because the alternative is no stack at all. Even if it would be a very reduced array of function names, it would be a hupe help already |
Right - I think the trace should be split into two parts: the native trace we already have, which shows your standard line of execution, and an augmentation that shows the handlers that have been executed earlier in the promise chain (probably stopping at the point where the promise was resolved?) Example:
|
Agree, a synchronous-stack-trace-look-alike probably will not be as helpful as something tailored toward helping to debug disjoint stacks. Seems axiomatically true, in fact :) Something like what @Twisol has proposed seems like a good direction. I'll add that we'd also need to track explicit rejections, e.g. Right now, I envision this working like current VMs work for exceptions: they are only loud about unhandled exceptions that reach the top of the call stack. So, perhaps we should only be loud about rejections that "escape" the end of a promise chain. We could record information about all rejections, but by default, I think logging all of them might be at least annoying and probably overwhelming (imagine if your browser JS console logged every thrown exception!). This is tricky because it's basically the halting problem for promises. At any point in time t you can't say that a rejected promise will never be handled, since at time t+1 someone might add a rejection handler to it, and handle the rejection by returning successfully, thus turning it back into a fulfillment which shouldn't have been logged. |
Once you've resolved the promise, and the chain is executing, 'then' should not add to the same chain. So you have a guarantee at time of execution: when a rejection reaches a dead end, no handler will ever be added to that chain in the same so-called timeline. It would be silly, IMO, to hold up an error on the off chance that someone may one day come back and, in an new timeline, handle the error. They should have chained onto it earlier if they expected the possibility of an error. |
Unfortunately, people make lots of mistakes with promises. We should strive to help them. If we assume they always do it right and never get confused, then this feature is less important. Adding a rejection handler after a promise has resolved/rejected is a valid use case. In a highly async system where promises are passed around freely, consumed from and given to 3rd party libraries, it's not really valid to assume that all handlers will always be added before a promise resolves or rejects. |
Yes, agreed - but the new handler must execute in a new "timeline" to preserve the semantics of 'then'. If a decision must be made as to when to throw an error, then it should be either at the end of the current timeline, or never. |
Thanks for clarifying. "Never" is what we do now, LOL :) After a timeout could be acceptable as well. Yes, that's arbitrary, but it's for debugging so I'm in favor of whichever is most helpful for the developer. I can imagine an approach where the current set of "currently known" unhandled rejections is maintained by adding and removing from it as promises are rejected, then later handled, etc etc. A periodic task could check that list for certain criteria, and then print diagnostic information for each unhandled rejection that matches. |
We should move any further discussion of possible approaches over to #109 |
Closing in favor of #109 |
Let's say I've this sequence of promise, all running async returning new promises again
Let's say that
this.butFailHere
will throw an error that gets passed tothis.handleError
. Is there any way I could show a stack of functions that have been called until the error occured?Desired output would be something like this
The text was updated successfully, but these errors were encountered: