Skip to content
This repository was archived by the owner on Apr 12, 2024. It is now read-only.

feature($http): allow differentiation between Request Error, Abort and Timeout #15847

Closed
wants to merge 3 commits into from
Closed

Conversation

frederikprijck
Copy link
Contributor

@frederikprijck frederikprijck commented Mar 22, 2017

This is a continuation of #15626

@frederikprijck
Copy link
Contributor Author

frederikprijck commented Mar 22, 2017

Travis fails:

20170322 114633.523 [3058] Starting secure remote tunnel VM...
20170322 114638.197 [3058] Secure remote tunnel VM provisioned.
20170322 114638.197 [3058] Tunnel ID: 92dd4c9c55354e3299ba2f05f924ab85
20170322 114638.553 [3058] GET https://saucelabs.com/rest/v1/angular-ci/tunnels/92dd4c9c55354e3299ba2f05f924ab85: HTTP response code indicated failure.
20170322 114638.553 [3058] Response: {"message": "API rate limit exceeded for public:angular-ci. See rate-limiting section in our API documentation."}.
20170322 114638.553 [3058] Error bringing up tunnel VM.
20170322 114638.553 [3058] Cleaning up.
20170322 114638.553 [3058] Removing tunnel 92dd4c9c55354e3299ba2f05f924ab85.

if (angular.isFunction(status)) return status;

return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
? [status, data, headers, statusText, xhrStatus]
: [200, status, data, headers];
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No xhrStatus here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

well, I could put a hardcoded 'success' there. But as 200 is already hardcoded, I was getting confused whether or not xhrStatus needs to be here.

@@ -1381,15 +1381,22 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
function handleResponse() {
var response = wrapped.response(method, url, data, headers, wrapped.params(url));
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder why we are coyping stuff that theoretically are primitive (such as status, statusText).

Copy link
Contributor Author

@frederikprijck frederikprijck Mar 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The copy was already there, I added the extra param in the same way. But yes, I was wondering the exact same thing. Maybe there used to be a reason :D

@@ -1381,15 +1381,22 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
function handleResponse() {
var response = wrapped.response(method, url, data, headers, wrapped.params(url));
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
var xhrStatus = copy(response[4]);
if (xhrStatus) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why the different code-paths?

Copy link
Contributor Author

@frederikprijck frederikprijck Mar 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apparently, I forgot to push things at home. I've fixed this already. I'll push it in 2 hours (+/-).

This was the quick and dirty check I did in order to stay backwards compatible (and not break 60 tests). But it's a better idea to break and fix them for this change.

So I've reduced it to a single path and fixed all 60+ braking tests.

if (angular.isFunction(status)) return status;

return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
: [200, status, data, headers];
? [status, data, headers, statusText, xhrStatus || (status === 200 ? 'success' : 'error')]
Copy link
Contributor Author

@frederikprijck frederikprijck Mar 22, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gkalpak It feels abit odd to be calculating xhrStatus (the above example isn't what it should be yet). I went down the path of an hardcoded 'success' value because of the hardcoded 200 on line 1350. Hardcoding 'success' made me wonder if we should be calculating xhrStatus based on status (if xhrStatus was not provided). But it brings some (unnessecary?) complexity in here.

Maybe it's a good idea to not set any xhrStatus based on the status, apart from the hardcoded 'success' on line 1350, and change this line (1349) to [status, data, headers, statusText, xhrStatus] keeping xhrStatus undefined if not provided, unless status is not a number (then xhrStatus would be 'success'). But this doesn't sound very consistent ... 😬

Copy link
Member

@gkalpak gkalpak Mar 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since xhrStatus is something that is generated by $httpBackend (i.e. it is not something that comes from the server, I think we should not even make it "trainable". I.e. you should:

  1. Remove the xhrStatus argument from createResponse (and other similar places).
  2. Generate xhrStatus inside createResponse, based on the same rules we use in $httpBackend (i.e. 200 <= status < 300 ? 'success' : 'error').
  3. Update existing tests. Add new tests that verify this new behavior.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Error as of 3xx or 4xx ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just realized 'success' is confusing. I was thinking it means something different than it actually does. Maybe we should change it to complete or something.
Then you should change both lines to xhrStatus || 'complete'.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So does that mean that response 300 up to 500 are to be considered not completed?

Copy link
Member

@gkalpak gkalpak Mar 28, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, all responses are considered 'complete' ('complete' means that the request completed, i.e. a response was received from the server, regardless of what the status of the response was).

Copy link
Contributor Author

@frederikprijck frederikprijck Mar 31, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@gkalpak

Then you should change both lines to xhrStatus || 'complete'.

Does it still make sense to use xhrStatus || 'complete' ?

function createResponse(status, data, headers, statusText, xhrStatus) {
    if (angular.isFunction(status)) return status;

    return function() {
      return angular.isNumber(status)
          ? [status, data, headers, statusText, xhrStatus || 'complete']
          : [200, status, data, headers, xhrStatus || 'complete'];
    };
  }

As we're always returning a status code, I guess we can consider it 'complete' in both situations? This would be in line with how this change is currently implemented in$httpBackend (none-mock).

Or is there any need to manually set the xhrStatus to anything else but 'complete' when using .respone() ?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOL - I contradict myself in these comments: #15847 (comment)
#15847 (comment)
It doesn't make sense to make it configurable imo, so let's remove the xhrStatus argument and make it 'complete' everywhere.

if (angular.isFunction(status)) return status;

return function() {
return angular.isNumber(status)
? [status, data, headers, statusText]
: [200, status, data, headers];
? [status, data, headers, statusText, xhrStatus || (status === 200 ? 'success' : 'error')]
Copy link
Member

@gkalpak gkalpak Mar 24, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since xhrStatus is something that is generated by $httpBackend (i.e. it is not something that comes from the server, I think we should not even make it "trainable". I.e. you should:

  1. Remove the xhrStatus argument from createResponse (and other similar places).
  2. Generate xhrStatus inside createResponse, based on the same rules we use in $httpBackend (i.e. 200 <= status < 300 ? 'success' : 'error').
  3. Update existing tests. Add new tests that verify this new behavior.

@katsos
Copy link
Contributor

katsos commented Jun 20, 2017

@frederikprijck do you need help on this one?

@frederikprijck
Copy link
Contributor Author

@Katos I did some cleaning up, depending on the review result it might be close to finished. Sorry for taking so long tho!

@gkalpak mind to review it?

@Narretz Narretz modified the milestones: 1.6.x, Backlog Jun 28, 2017
@Narretz
Copy link
Contributor

Narretz commented Jun 29, 2017

@gkalpak I see this is labelled as "needs breaking change". I can't see how this is breaking atm :/

@@ -1382,14 +1382,14 @@ function createHttpBackendMock($rootScope, $timeout, $delegate, $browser) {
var response = wrapped.response(method, url, data, headers, wrapped.params(url));
xhr.$$respHeaders = response[2];
callback(copy(response[0]), copy(response[1]), xhr.getAllResponseHeaders(),
copy(response[3] || ''));
copy(response[3] || ''), copy(response[4]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What's with the 7-space indentation? 😕

Copy link
Contributor Author

@frederikprijck frederikprijck Jul 3, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well, the linter agrees so ... 🙈 (on a serious note: no clue, I'll get it removed before merging)

@gkalpak
Copy link
Member

gkalpak commented Jul 3, 2017

@Narretz, (although not likely) it could break tests that assert an $http promise was resolved with an object containing certain properties. E.g.:

const mySpy = jasmine.createSpy('mySpy');

$httpBackend.whenGET('/foo').respond('bar');
$http.get('/foo').then(mySpy);
$httpBackend.flush();
    
expect(mySpy).toHaveBeenCalledWith({
  data: 'bar',
  status: 200,
  statusText: '',
  headers: jasmine.any(Function),
  config: jasmine.any(Object),
});

This would previously pass, but it will fail with this change because the xhrStatus property will be missing.

I admit the possibility this affects anyone is tiny, but still possible. I added the label, so we can discuss it when the time comes (which is about now 😃).

@Narretz
Copy link
Contributor

Narretz commented Jul 4, 2017

i think the feature is too useful and the possible breaking change to minor to put this into 1.7. I'd say put it into 1.6

@gkalpak
Copy link
Member

gkalpak commented Jul 4, 2017

OK. Let's do this 😃

@gkalpak gkalpak closed this in e872f0e Jul 4, 2017
gkalpak pushed a commit that referenced this pull request Jul 4, 2017
…rt, timeout

Previously, it wasn't possible to tell if an `$http`-initiated XMLHttpRequest
was completed normally or with an error or it was aborted or timed out.
This commit adds a new property on the `response` object (`xhrStatus`) which
allows to defferentiate between the possible statuses.

Fixes #15924

Closes #15847
@evanjmg
Copy link

evanjmg commented Jan 7, 2021

did this end up in a release somewhere?

@frederikprijck
Copy link
Contributor Author

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants