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
Throw in promise-fulfillment swallows exception #30
Comments
Arggh. I guess github issues are not plain text. |
Agreed. However, swallowing exceptions is desirable behavior since it unifies synchronous and asynchronous exception handlers. I wavered on this for a while, but Irakli Gozashvili convinced me it was necessary for the flipside developer ergonomics. The present stopgap is to terminate all promise chains with a .end() call. function main() {
// foo returns a promise, but we don't look at the return so we lose.
return foo(promiseLoaded());
}
main().end(); I am playing with the idea of having another stop-gap solution: using console.log to write a live array to the console. I would then add exceptions to the array when they are swallowed, and remove them when they are handled. It would be much more awesome to extend Firebug with something like Causeway, where I could use something like a (Issues are in markdown format. You can use Github’s proprietary extension to Markdown for literal bodies: three backticks on top and on bottom. If you want syntax highlighting, put the file extension on the same line as the initial backticks.) |
How about a Q.debug = true mode? The problem with .end() is of course you fail to call it just in the cases you need it. I suppose to be realistic the ergonomics of Q are pretty horrible in that the call stack goes from "oh, my code" to "OMG what is this code?" A big step could be showing the dev the chain you describe. What is Causeway? (This is one of the areas I want to work on, tho not in Firebug. Same issues face all other async libs like require.js.) |
Causeway is a distributed debugger. http://wiki.erights.org/wiki/Causeway MarkM talked about it in Belgium recently. http://www.youtube.com/watch?v=w9hHHvhZ_HY |
Thanks! Since we are in the same process I think we can do something non-post morteum |
@johnjbarton I certainly have ambitions for a live visualization. |
The way I handle this issue in my Q-like mini implementation of deferreds/promises is by logging any exception thrown from success/error callbacks in addition to rejecting the derived promises with the exception being the rejection reason. So essentially, my stance is that an exception unhandled in a callback represents a programming error and should be logged. To signal that derived promises should be intentionally rejected, one must return a rejected promise instead of a value or an exception. So Q could be fixed to differentiate between these two scenarios (currently it does not):
This approach has been working quite well for me so far and I believe that it is in the "spirit" of deferreds/promises. From a quick glance at the code, a good place to start would be to log these exceptions: Keep in mind that this is a breaking change, because throwing an exception and returning a rejected promise is not the same thing any more. So developers would likely have to go back and fix the old code that merely relies on throwing, rethrowing or unhandling an exception. |
@IgorMinar this is how Q worked about a year ago. Unfortunately it’s harder to go back than to go forward. |
@kriskowal can share more details about why this logging was removed? |
@IgorMinar I was logging the exception and forwarding a rejection. This led to dirty and confusing double-logging if the rejection was handled properly. I was also of the mind that exceptions would be decreed to be programmer errors and rejections were for intentional errors. However, this did not pan out in practice. A lot of existing systems throw exceptions for non-error cases, and these do need to get trapped in rejections. Having all thrown exceptions reported in every frame is not an option, and terminating a services because of an asynchronously handled exception is not an option. It’s really quite tricky. It would be a much better option to just visualize the promise graph for debugging purposes. |
@kriskowal if an existing system communicates a result of an action by throwing an exception then the success callback should expect that (since its part of the contract of the api that it relies on). Isn't it reasonable to expect the developer of the success callback to wrap the call into try/catch and reject return rejection if an expected exception is thrown? I actually use an alternative method - I made the logger configurable, so at runtime I can choose to display the exception logs during development and in production I silence them. |
@IgorMinar It really just isn’t practical. A lot of these functions only throw in exceptional conditions, so a cautious developer would have to try/catch every third party function. I really am considering a gamut of solutions. One of them discussed earlier with @asutherland would be to allow the promise library to be instantiated with logging emitters. It’s still on the table if someone has the time. |
Another useful causeway link (that I don't believe the above links reference) is a more recent JS version with live-ish examples: |
I believe this issue has played out. We have |
@kriskowal is there a write-up on how to use |
So I love Q, and prefer it, except this error handling case is really difficult on me. https://github.com/tildeio/rsvp.js/#error-handling In RSVP, they have an "error" event that fires, meaning as a user you can have some |
@randallb Q.onerror is a thing that exists. |
well... i fail at reading docs then. Thank you very much. |
Ah it'll only catch errors if you use the |
@randallb Sorry, that went out too soon. It probably does not do what you want. Q.onerror gets called for unhandled rejections that would get rethrown. It does not report errors that have been caught but have not been handled. There is a limbo for errors, where they might be observed and handled in a future turn. In the trivial case: var rejected = Q.reject(new Error('boo'));
Q.delay(1000, function () {
return rejected;
}).done(); This is too trivial a case. The problem will show up if you use promise queues or asynchronous iterators / readers, since the producer and the consumer operate independently. var reader = Reader([1, 2, 3, 4])
.map(function (value) {
if (value % 2) throw new Error('how odd');
});
// later...
reader.next().catch(function (error) {
}); So while we could surface unhandled errors in the same event loop turn like RSVP, it would be noisy for legitimate cases. I need to refresh my context, but there are ongoing plans to surface other events like, ondefer, onresolve, onreject, onhandle that would be able to drive an inspector that would show "pending deferred" promises and "rejected unhandled" promises for any given moment. |
If Q
couldn't we implement a function that runs once per event loop and throws unhandled errors if their corresponding promise has been garbage collected? Weak references to promises that have been garbage collected but have no outstanding unhandled errors are just dropped as well. Since this comes at a performance cost and not every JavaScript engine Q runs on would necessarily support ES6's WeakMaps or whatever we use to implement this, this feature could just be disabled (either explicitly by client code or implicitly, if Q detects weak references aren't supported), and people who either don't want it or can't use it can just keep using Couldn't that work pretty well? Granted, if the object takes too long to be garbage collected, the program may keep running for a while before the exception is logged and/or the process is killed, but that's better than nothing in my case, at least. |
Yes, that would work wonderfully. Unfortunately weak maps are not weak references. |
From https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/WeakMap:
Are you talking about this? It looks like you can kind of do it if you maintain a list of things that should be on the WeakMap, and check them periodically, no? |
How would you maintain that list? (Answer: using a strong reference.) |
True. How about adding unique IDs to promises and keeping a list of those, though? |
How would you then find out if the promise is in the weak map? |
Crap, true. They're not enumerable. That sucks :( |
@n2liquid WeakMap is what it is for good reasons and is deliberately not for this use-case. There is a proposed WeakRef that provides a post mortem notification callback. We had to wait for TC39 to specify an event loop to get Promise. We’ll have to wait for TC39 to specify garbage collection before we’ll get WeakRef. Maybe we’ll get it someday, but that sounds hard. Members TC39 would prefer not to introduce a feature that would increase the observable differences between engines and specifying GC could seriously paint the language into a corner. |
@kriskowal I see, it's a very tricky thing, indeed. But boy, I just don't feel safe that even things like TypeErrors would just be swallowed if I forget to use I guess dumping unhandled errors to the console before exiting the program is as good as it'll get for me (I'm not writing a server, so this is reasonable in this particular case). |
https://github.com/kriskowal/q/blob/v1/q.js#L158 I'm sorry, but really?? This is all kinds of wrong. Not only are we swallowing exceptions, we're destroying the stack too. I don't know what the solution is, but this ain't it. |
I don't think rethrowing destroys the stack. The stack will be that of when the Error object was created. |
|
The text was updated successfully, but these errors were encountered: