Bug Tracker

Opened 9 years ago

Closed 9 years ago

#14683 closed bug (fixed)

ajax: Exceptions thrown synchronously by xhr.send are not caught

Reported by: jakob@… Owned by: jaubourg
Priority: blocker Milestone: 2.1.1
Component: ajax Version: 2.0.3
Keywords: Cc: jaubourg
Blocked by: Blocking:

Description

When an invalid url is passed to $.get, Chrome throws an NetworkError and no callbacks are called.

Expected result: Both fail() and always() should get called.

Code sample:

$.get('//example.com:80q').then(function () {
    console.log('not called');  
}).fail(function () {
    console.log('not called');  
}).always(function () {
    console.log('not called');  
});

http://jsfiddle.net/4FxSt/

Full error message:

XMLHttpRequest cannot load http://example.com:80q/. Cross origin requests are only supported for HTTP. jquery-2.0.2.js:7858
Uncaught NetworkError: A network error occurred. jquery-2.0.2.js:7321

Tested versions: 2.0.2, 2.0.3

Reproduced in: Chrome 31.0.1650.63

Couldn't reproduce in: IE10, Firefox 26

This does work as expected using jQuery 1.10.2, which makes me think it's an regression. http://jsfiddle.net/4FxSt/1/

Change History (14)

comment:1 Changed 9 years ago by dmethvin

Cc: jaubourg added
Component: unfiledajax
Priority: undecidedhigh
Status: newopen

Agreed, this seems like it should call the error callback.

comment:2 Changed 9 years ago by dmethvin

Milestone: None2.next

comment:3 Changed 9 years ago by njhamann

This appears because of the added onerror listener to xhr at https://github.com/jquery/jquery/blob/master/src/ajax/xhr.js#L110

Because this successfully fires (in chrome and safari) when trying an invalid url the try catch at https://github.com/jquery/jquery/blob/master/src/ajax.js#L649 throws an error instead of calling done() when chrome triggers a networkerror. Unfortunately throwing this error prevents the deferred callback from being called.

This can be fixed by delaying throwing the error by adding a setTimeout around throw e.

The alternative fix for this issue would be to remove the on error callback that is not consistently being called with all browsers or remove throw e line.

I will submit a pull request soon. Any feedback would be appreciated.

comment:4 Changed 9 years ago by dmethvin

Awesome, thanks for the diagnosis!

comment:6 Changed 9 years ago by jaubourg

That's as edgy a case as I can imagine... and we tend to go by the "garbage in, garbage out" motto. The likelihood of such an invalid url not resulting from a programming error is very slim to say the least.

I'd vote against the "fix" but I'll let other weight in on this.

comment:7 Changed 9 years ago by jakob@…

My use case was that to let the user configure the URL to an API and verify it by sending a simple GET request. Of course I could make sure the URL is valid first, but I expected jQuery to handle that (and I assume others do, too).

I can't comment on the fix though because I don't know a lot about the internals of jQuery.

comment:8 Changed 9 years ago by jaubourg

Oh I wasn't implying your code was wrong or anything. It's just that it's a hack on top of a hack, albeit unavoidable given the goal you set here.

I'd need to check in the minutia of the XHR spec but if Chrome is going against the standard, then we can report a bug there and expect a fix in a timely fashion.

For the time being, you can catch the exception yourself (in parallel of an error handler) to cover your bases. Correct?

comment:9 Changed 9 years ago by jaubourg

I can't reproduce in Chrome 32.0.1700.76 m nor in Chrome 34.0.1803.2 canary using this fiddle: http://jsfiddle.net/6Kjhr/1/

Am I "doing it wrong"(tm)?

comment:10 Changed 9 years ago by scottgonzalez

That fiddle is using 1.10.2, which jakob said works. Look at the first fiddle (remove the /1).

Last edited 9 years ago by scottgonzalez (previous) (diff)

comment:11 Changed 9 years ago by jaubourg

/slapforehead :|

Yep, I can reproduce now: http://jsfiddle.net/6Kjhr/2/

Thanks Scott.

comment:12 Changed 9 years ago by jaubourg

tl;dr: this is a bug in Chrome but there's a clean workaround for the time being.

Longer version...

Confirmed this is a Chrome bug, see https://dvcs.w3.org/hg/xhr/raw-file/tip/xhr-1/Overview.html#the-open%28%29-method, point 9.

Chrome fails to identify the URL as illegal and throws on send thinking it's a CORS violation.

Interestingly, Chrome calls onerror before throwing, apparently not onreadystatechange hence the difference in behaviour between 1.x and 2.x. For the latter, state is too "advanced" when the desperate-try-catch-of-no-return is reached. jQuery thus thinks there's an exception in a callback attached to the internal Promise or in its own code, something we clearly don't wanna shadow.

So the internal Deferred is resolved, but since everything happens synchronously, there's no time to attach the handlers. It works, however, if you use the callbacks options like so:

$.ajax( "//example.com:80q", {
    success: function () {
        log( "success", arguments );  
    },
    error: function () {
        log( "error", arguments );  
    },
    complete: function () {
        log( "complete", arguments );  
    }
} );

Those are deprecated though but there's a way to use the Promise interface: beforeSend, which gives early access to the jqXHR instance:

$.ajax( "//example.com:80q", {
    beforeSend: function( jqXHR ) {
        jqXHR.done( function () {
            log( "done", arguments );  
        } ).fail( function () {
            log( "fail", arguments );  
        } ).always( function () {
            log( "always", arguments );  
        } );
    }
} );

You can see it in action in http://jsfiddle.net/7LAVY/

Last edited 9 years ago by jaubourg (previous) (diff)

comment:13 Changed 9 years ago by jaubourg

Milestone: 2.next2.1.1
Owner: set to jaubourg
Priority: highblocker
Status: openassigned
Summary: ajax: Invalid url triggers NetworkError in Chromeajax: Exceptions thrown synchronously by xhr.send are not caught

I re-qualified the bug.

There are other valid circumstances where xhr.send may throw synchronously (data that cannot be serialized being one of them) so, despite the fact there's a bug in Chrome in your specific use-case, we still have to protect against these exceptions.

Since this is an xhr issue, the fix should be in the xhr transport, see the following PR: https://github.com/jquery/jquery/pull/1497

Thanks a bunch for discovering this, Jakob.

comment:14 Changed 9 years ago by jaubourg

Resolution: fixed
Status: assignedclosed

Ajax: Protect against exceptions thrown synchronously by xhr.send

When xhr.send throws an exception synchronously, the onerror handler may have been called already which, unchecked, makes the exception bubble up outside of jQuery.ajax.

We now catch the exception pre-emptively and only rethrow if we know it hasn't already been notified through the onerror handler.

Fixes #14683

Changeset: 01c360f96390ff16edfe65ef3b34e167087ef645

Note: See TracTickets for help on using tickets.