-
Notifications
You must be signed in to change notification settings - Fork 396
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
Listener functions fail silently #18
Comments
Hey @bjoerge, The goal is for when.js to translate uncaught exceptions into promise rejections. So, on one hand, your example is kind of like a So, in this case, the following will catch the error: var deferred = when.defer();
deferred.then(function() {
i.will.produce.ReferenceError
}).then(null, function(err) {
// Catches the ReferenceError from above
console.error(err);
});
deferred.resolve(); However, I certainly agree that it's not realistic to try to account for accidental developer mistakes like ReferenceErrors everywhere like this! Which is why your example is particularly interesting :) It's a developer error, and it would be nice if IMHO, if when.js did a function doStuff() {
var deferred = when.defer();
// Simulate something async, like an XHR
setTimeout(deferred.resolve);
return deferred.then(function(result) {
if(something) {
// Do awesome stuff and compute new result
return awesomeResult;
} else {
// It's perfectly acceptable to use exceptions like this to reject subsequent
// promises. This allows for a familiar programming model, and allows existing
// functions that aren't promise-aware, and thus may throw exceptions anyway
// to be used as promise callbacks.
throw new Error('this causes subsequent promises in the chain to reject');
}
});
} The caller can handle the exception as a promise rejection: when(doStuff(), handleAwesomeResult, handleFailure); Ok, after all that, like I said, I do agree that it would be nice if If we added a debug mode, do you think that would be a reasonable solution? |
There's some related discussion over on #13 about providing a Another option might be for when.js to identify rejected promises that don't have any errbacks registered with them, and then log a last-ditch error message in that case. |
We've been focused on getting 1.0 out the door, but now that that's done, I have some ideas for how to handle this. |
Hey Brian, this totally slipped out of my radar for a while... sorry about that! Thanks for your answer, I see your perfectly good reasons for the try/catch block. However, are there any reason to not rethrow the error again after rejecting the promise? In that case the error can just propagate up the call chain and be handled as usual in the application code. At least thats what I would expect it to do :-) In any case, I guess the important thing here is that this is a gotcha developers should be aware of when using when.js (I personally spendt quite a while trying to figure out why things didn't work the way I expected before figuring it out). Maybe adding a note about it to the README could be an idea? Beyond that, thanks for a great library! |
This problem is really getting in the way. Every JS error, e.g. "ReferenceError" gets silently swallowed always everywhere, whenever when() is used which makes it impossible to develop. For example, tests fail silently instead of displaying what error happened, etc. |
@KidkArolis: when.js 1.1.0 has a new debug module that is starting to experiment with ways of dealing with this. We're definitely looking for feedback, so if you haven't tried it out yet, please give it a go. One of the essential problems is that there are 2 classes of exceptions here: 1) exceptions that represent coding errors, e.g. ReferenceError that the application shouldn't handle, and 2) "legitimate" application exceptions that the application can and should handle. Ideally we'd like 1 to propagate all the way to the host environment so you get the usual failure/stacktrace/etc that you're accustomed to seeing, and we'd like 2 to be transformed into a rejection that the application can handle via promise rejection handler. In addition, for asynchronous operations, exceptions will be happening in a future event loop turn (and thus a different stack frame) than the call to Right now, when/debug doesn't do anything super-fancy. It allows you to "tag" promises with an identifier you'll recognize in the log, and logs some info, including the tag and rejection value, when promises reject. Here are some questions:
There's probably a million different things it could do, so we wanted to get it out there for folks to try so that we can tweak it to make it truly useful. |
I just pushed a change to the when-debug branch that forces certain built-in Error types (RangeError, ReferenceError, SyntaxError, and TypeError) to propagate to the host environment when they occur in a promise handler. It may be way to heavy-handed, but I figured it was at least worth experimenting with to see if it helps us figure out a path to better ways to debug promises. If you guys get a chance, please try it out. I'd love to hear your feedback. Thanks! |
Could you give an example of a "legitimate" application exceptions that the application can and should handle? Again, without having thought about it too much, it seems like if the developer decides not to handle errors, when.js shouldn't handle them itself. I mean, if you are expecting something to potentially fail within your when.js callbacks, then you have an should explicitly try/catch. I'll give when-debug a shot. Great work otherwise, writing promise based code is quite revealing, especially in nodejs :) I'll also 'move' to issue #28 now, as the discussion there got more elaborate. Should this one be closed as a duplicate? On the other hand, #28 is getting 'polluted' with other topics.. :) |
@KidkArolis Thanks, I have some ideas for how to make when.js even more useful in node, such as promisify-ing node functions that use the "standard" node callback style. Let me know what you think after trying tl;dr :) In my experience, it's fairly common to use exceptions to indicate errors at one level of an application that can be handled at a higher level--not necessarily the immediate caller. For example, you might have a parser that you use to parse some user input, and it might throw exceptions that you let propagate to a higher layer of your application that coordinates validation and display of feedback to a user. One important characteristic of exceptions is that only the code that generates the exception, and the code that catches the exception needs to have any special code: Interestingly enough, promises can be used in a similar way by returning them up the stack instead of passing them down the stack. A low level routine can return a promise, which can then be propagated (via The typical model for handling asynchrony in JS is to pass callbacks down the stack, but I've come to believe that with promises, a better model is to return them up the stack. I wrote a bit about that here, and here |
I keep commenting try/catch blocks in when.js file everytime I use it. For example, just now, I was writing a test like this in Buster.JS: test1: function (done) {
doStuff().then(function (result) {
assert.equals(result, 42);
done();
});
} When the assertion fails, Buster would normally report this test as failed. However, when.js seems to have handled the exception and so Buster.js instead reports that the test timed out. |
I just got bitten by commenting those try/catch blocks. try {
var result = JSON.parse(response);
deferred.resolve(result);
} catch (e) {
deferred.reject(e);
} was executing both resolve and reject, because somewhere up the stack there was an exception, that bubbled up to this try/catch block. Normally, when.js would have handled that, but I obviously messed it up by commenting try/catches in when.js itself. How do I solve the buster.js problem though? |
Hey, I'm traveling and have to respond from my mobile right now, so With async tests in buster, you need to ensure that done() is called Take a look at when.js's own unit tests for ways to do that. For On Jun 20, 2012, at 6:26 PM, Karolis Narkevicius
|
Ok. It's starting to make more sense :) Buster.js documentation is discussing this exact problem about assertions throwing exceptions and how that should be handled in the async tests (http://busterjs.org/docs/test/test-case/#async-tests). Basically I changed the code to something like test1: function () {
return doStuff().then(function (result) {
assert.equals(result, 42);
});
} I return a promise instead of using the done() callback. And some thinking out loud now.. |
I also like the idea you mentioned earlier of "identify[ing] rejected promises that don't have any errbacks registered with them, and then log a last-ditch error message in that case." |
Finally back to a real keyboard, so I'll try to catch up on answering :) Returning a promise from buster's unit tests is great. It's the ideal, imho, and I'm glad that worked out. I had not been doing it because at some point I was having trouble with it. I should revisit it for when.js's unit tests because it's so much cleaner, and matches the "one assertion per test" model. Yeah, it's tough, if not impossible, for when/debug to know which exceptions to log, since where and how to handle can be very application and architecture specific (plus dev-time errors, like SyntaxError). I think the best it can do is try to guess, and err on the side of being overly verbose. For example, it currently guesses that SyntaxError, TypeError, and the like are programmer error, and rethrows them in a different stack frame so they are uncatchable and propagate to the VM. The last paragraph in your previous comment is right on the mark: returning promises up the stack is the way to go. That guarantees that a rejected promise at a low level will propagate up to a higher level in the application where it should be handled ... i.e. promises behave like an async analog to exceptions. The big difference, of course, is that Javascript VMs have no native understanding of promises, so they can't automatically print stack traces for unhandled rejections like they can for unhandled exceptions. Related to that, if you find yourself creating lots of deferreds, that can be a sign that your promise usage is "inverted", that is, you're passing too much down the stack. The purpose of a deferred really is to adapt to an environment whose basic building block is callbacks. Once lower-level code has generated promise, typically by using a deferred, code above it can usually just return the result of calling Hope that helps, rather than confusing things! |
Closing, as there have been lots of improvements to when/debug, and there hasn't been any activity here for a while |
The try/catch wrapper around the code that notifies listener functions will swallow up any error occurring in the call chain of a listener function (./when.js#L281). This is not very desirable as any errors inside a listener function will be suppressed.
Consider the following example:
This will fail silently, which is clearly not the expected behavior (I would expect a message to appear in the error log of the browser/environment).
The text was updated successfully, but these errors were encountered: