-
Notifications
You must be signed in to change notification settings - Fork 27.5k
ugly solution for unexpected request fail on verify no outstanding request #16150
Conversation
Looks like this break the promises-aplus-tests. |
56e8cc8
to
76e5a15
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, I couldn't come up with anything "less ugly" 😃
So, happy to go with this approach.
Thx for looking into it @marcin-wosinek 👍
src/ngMock/angular-mocks.js
Outdated
new Error('No response defined !') : | ||
new Error('Unexpected request: ' + method + ' ' + url + '\n' + | ||
(expectation ? 'Expected ' + expectation : 'No more request expected')); | ||
|
||
// TODO ugly! | ||
error.rethrow = true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please rename to $$rethrow
or $$passToExceptionHandler
.
src/ngMock/angular-mocks.js
Outdated
new Error('No response defined !') : | ||
new Error('Unexpected request: ' + method + ' ' + url + '\n' + | ||
(expectation ? 'Expected ' + expectation : 'No more request expected')); | ||
|
||
// TODO ugly! |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove the ugly
comment 😛
An actual comment would fine actually. Something like:
In addition to be being converted to a rejection, this error also needs to be passed to
the$exceptionHandler
and be rethrown (so that the test fails).
src/ng/q.js
Outdated
@@ -364,6 +364,10 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { | |||
} | |||
} catch (e) { | |||
rejectPromise(promise, e); | |||
// TODO ugly! | |||
if (e && e.rethrow === true) { | |||
exceptionHandler(e); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A comment would be nice here too. E.g. something like:
This error is explicitly marked for being passed to the
$exceptionHandler
.
Thanks for the feedback! looks that it fixed the issues on tests too:) |
src/ng/q.js
Outdated
@@ -364,6 +364,10 @@ function qFactory(nextTick, exceptionHandler, errorOnUnhandledRejections) { | |||
} | |||
} catch (e) { | |||
rejectPromise(promise, e); | |||
// This error is explicitly marked for being passed to the $exceptionHandler | |||
if (e && e.$$passToExceptionHandle === true) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
...Handler
(with an r
in the end).
src/ngMock/angular-mocks.js
Outdated
new Error('No response defined !') : | ||
new Error('Unexpected request: ' + method + ' ' + url + '\n' + | ||
(expectation ? 'Expected ' + expectation : 'No more request expected')); | ||
|
||
// In addition to be being converted to a rejection, this error also need to be passed to |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
need --> needs
We talked about this yesterday in the meeting and @petebacondarwin had another idea how to implement this, maybe he can outline it here |
The general concern was with potential collisions with user code by checking a "custom" property on the error objects. I accept that this is mitigated by the use of Since @mgol suggested that an even cleaner approach would be to override the @mgol also pointed out that this "special" behaviour would not strictly be in keeping with the Promises A+ spec, even though it would pass the tests... |
If it's passing the test... 😛 Why would it not be compiant with Promises/A+? Happy to go with the special Error class, btw. |
Tests can almost never cover 100% of the spec. In this case, the spec doesn't allow to treat some rejections/errors differently. |
OOC, where does it say so? |
The spec describes what needs to happen when an exception is thrown in a
handler. It doesn't specify any requirements for those exceptions, hence
all the rules have to apply for every exception type.
|
What the spec describes does happen with all exceptions. But for some exceptions, additional stuff may happen. But I do see your point. (I feel like we've had this conversation before 😛) |
@gkalpak Additional stuff may happen (we can then only discuss if it lies in the spirit of the spec) but that's not the point here. The spec mandates all exceptions need to be passed to the |
The promise will be rejected as usual, but the error will also be passed to the |
Has anyone implemented our agreed solution here? |
4e80e7e
to
8f99e67
Compare
I don't think so. I think we wanted to wait for @gkalpak to return, as he reviewed this initially |
As per #16150 (comment), I am happy to go with the special Error class. @marcin-wosinek, would you be up to updating the PR with the new implementation (as described in #16150 (comment))? |
yes, I'm about to update the implementation. |
51a9af9
to
8f480de
Compare
Looks it failed before due to some e2e glitch. It's ready to final review. |
src/ngMock/angular-mocks.js
Outdated
(expectation ? 'Expected ' + expectation : 'No more request expected')); | ||
|
||
// In addition to be being converted to a rejection, this error also needs to be passed to | ||
// the $exceptionHandler and be rethrown (so that the test fails). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can you move that comment to where the error is created (i.e. right above var error = wasExpected ?
).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sure
src/ng/q.js
Outdated
this.name = '$$PassToExceptionHandlerError'; | ||
this.message = message; | ||
this.stack = (new Error()).stack; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"Subclassing" Error is actually tricky. For example, different browsers use different properties (such as line
, sourceId
, stackArray
). Other browsers do not populate the stack
property until the error is actually thrown (so (new Error).stack
will be empty). And finally, the stack trace will be a little confusing by showing a slightly different location:
# Expected
Error: Some error message
at <where-the-error-was-actually-thrown>:<line>:<column>
...
# Actual
Error
at PassToExceptionHandlerError (<file>:<line>:<column>)
at <where-the-error-was-actually-thrown>:<line>:<column>
...
Here is what we do in ngMock
to mitigate these issues:
angular.js/src/ngMock/angular-mocks.js
Lines 3109 to 3118 in ecc09a4
var ErrorAddingDeclarationLocationStack = function ErrorAddingDeclarationLocationStack(e, errorForStack) { | |
this.message = e.message; | |
this.name = e.name; | |
if (e.line) this.line = e.line; | |
if (e.sourceId) this.sourceId = e.sourceId; | |
if (e.stack && errorForStack) | |
this.stack = e.stack + '\n' + errorForStack.stack; | |
if (e.stackArray) this.stackArray = e.stackArray; | |
}; | |
ErrorAddingDeclarationLocationStack.prototype = Error.prototype; |
angular.js/src/ngMock/angular-mocks.js
Lines 3122 to 3128 in ecc09a4
var errorForStack = new Error('Declaration Location'); | |
// IE10+ and PhanthomJS do not set stack trace information, until the error is thrown | |
if (!errorForStack.stack) { | |
try { | |
throw errorForStack; | |
} catch (e) { /* empty */ } | |
} |
angular.js/src/ngMock/angular-mocks.js
Line 3162 in ecc09a4
throw new ErrorAddingDeclarationLocationStack(e, errorForStack); |
Another idea would be wrap the error in a non-error object and unwrap it in $q
:
src/ng/q.js
+$Q.$$PassToExceptionHandlerWrapper = function PassToExceptionHandlerWrapper(error) { this.error = error; }
...
function processQueue(state) {
...
} catch (e) {
+ var passToExceptionHandler = e && e instanceof PassToExceptionHandlerWrapper;
+ e = passToExceptionHandler ? e.error : e;
rejectPromise(promise, e);
+ if (passToExceptionHandler) {
+ exceptionHandler(e);
+ }
}
src/ngMock/angular-mocks.js
+try {
+ // Throw the error to ensure the `stack` property is populated.
throw wasExpected ?
new Error('No response defined !') :
new Error('Unexpected request: ' + method + ' ' + url + '\n' +
(expectation ? 'Expected ' + expectation : 'No more request expected'));
+} catch (err) {
+ throw new $q.$$PassToExceptionHandlerWrapper(err);
+}
Then again, @mgol will say this violates the Promises/A+ spec and he will be right (since we will be rejecting with a different object than the one that was thrown) 😟
Given all that, I think I might prefer the original "custom $$passToExceptionHandler
property on the error" approach 😞
What do others think?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are these issues with custom Errors still valid on the browser we support? I thought they were more IE8ish??
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The "no stack until thrown" issue is IE10+ (and PhantomJS which is not officially supported).
I am not sure about the extra properties, but iirc they are Safari-specific (could be wrong though).
The "extra line in stack trace" issue is by design, but should be easy to work around.
Basically all the issues are "work-around-able". The question is, is it worth the extra complexity?
(I don't feel strongly about it, though. Happy to go either way.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So there are 3 options:
- Field on normal error (previous solution
- Class inheriting from error (current code in PR, plus maybe changes to the way class defined)
- Class wrapping error
Which way should be implemented?
73ad3f0
to
0b894c5
Compare
0b894c5
to
bd53d3a
Compare
…outstanding request
bd53d3a
to
94ce1de
Compare
master was updated with first suggestion from: the second suggestion is applied in: the original proposition is in: |
@marcin-wosinek - thank you so much for you work and patience on this PR. We had a long chat again today and have finally decided, after all to go with your original proposal (https://github.com/marcin-wosinek/angular.js/tree/16150-c). I will squash and merge the two commits from that branch, so there is no need to update this PR any further. I am sorry it has taken so long to deal with this. |
Cool; great to have it finally land:) |
What kind of change does this PR introduce? (Bug fix, feature, docs update, ...)
Bug fix
What is the current behavior? (You can also link to an open issue here)
#15855
What is the new behavior (if this is a feature change)?
same as before the regression
Does this PR introduce a breaking change?
no
Please check if the PR fulfills these requirements
Other information: