jQuery ajax (jsonp) ignores a timeout and doesn't fire the error event
JqueryAjaxError HandlingTimeoutJsonpJquery Problem Overview
To add some basic error handling, I wanted to rewrite a piece of code that used jQuery's $.getJSON to pull in some photo's from Flickr. The reason for doing this is that $.getJSON doesn't provide error handling or work with timeouts.
Since $.getJSON is just a wrapper around $.ajax I decided to rewrite the thing and surprise surprise, it works flawlessly.
Now the fun starts though. When I deliberately cause a 404 (by changing the URL) or cause the network to timeout (by not being hooked up to the interwebs), the error event doesn't fire, at all. I'm at a loss as to what I'm doing wrong. Help is much appreciated.
Here's the code:
$(document).ready(function(){
// var jsonFeed = "http://api.flickr.com/services/feeds/photos_public.gne"; // correct URL
var jsonFeed = "http://api.flickr.com/services/feeds/photos_public.gne_______"; // this should throw a 404
$.ajax({
url: jsonFeed,
data: { "lang" : "en-us",
"format" : "json",
"tags" : "sunset"
},
dataType: "jsonp",
jsonp: "jsoncallback",
timeout: 5000,
success: function(data, status){
$.each(data.items, function(i,item){
$("<img>").attr("src", (item.media.m).replace("_m.","_s."))
.attr("alt", item.title)
.appendTo("ul#flickr")
.wrap("<li><a href=\"" + item.link + "\"></a></li>");
if (i == 9) return false;
});
},
error: function(XHR, textStatus, errorThrown){
alert("ERREUR: " + textStatus);
alert("ERREUR: " + errorThrown);
}
});
});
I'd like to add that this question was asked when jQuery was at version 1.4.2
Jquery Solutions
Solution 1 - Jquery
jQuery 1.5 and higher have better support for error handling with JSONP requests. However, you need to use the $.ajax
method instead of $.getJSON
. For me, this works:
var req = $.ajax({
url : url,
dataType : "jsonp",
timeout : 10000
});
req.success(function() {
console.log('Yes! Success!');
});
req.error(function() {
console.log('Oh noes!');
});
The timeout seems to do the trick and call the error handler, when there is no successful request after 10 seconds.
I did a little blogpost on this subject as well.
Solution 2 - Jquery
This is a known limitation with the native jsonp implementation in jQuery. The text below is from IBM DeveloperWorks
> JSONP is a very powerful technique for > building mashups, but, unfortunately, > it is not a cure-all for all of your > cross-domain communication needs. It > has some drawbacks that must be taken > into serious consideration before > committing development resources. > First and foremost, there is no error > handling for JSONP calls. If the > dynamic script insertion works, you > get called; if not, nothing happens. > It just fails silently. For example, > you are not able to catch a 404 error > from the server. Nor can you cancel or > restart the request. You can, however, > timeout after waiting a reasonable > amount of time. (Future jQuery > versions may have an abort feature for > JSONP requests.)
However there's a jsonp plug-in available on GoogleCode that provides support for error handling. To get started, just make the following changes to your code.
You can either download it, or just add a script reference to the plug-in.
<script type="text/javascript"
src="http://jquery-jsonp.googlecode.com/files/jquery.jsonp-1.0.4.min.js">
</script>
Then modify your ajax call as shown below:
$(function(){
//var jsonFeed = "http://api.flickr.com/services/feeds/photos_public.gne"; // correct URL
var jsonFeed = "http://api.flickr.com/services/feeds/photos_public.gne_______"; // this should throw a 404
$.jsonp({
url: jsonFeed,
data: { "lang" : "en-us",
"format" : "json",
"tags" : "sunset"
},
dataType: "jsonp",
callbackParameter: "jsoncallback",
timeout: 5000,
success: function(data, status){
$.each(data.items, function(i,item){
$("<img>").attr("src", (item.media.m).replace("_m.","_s."))
.attr("alt", item.title)
.appendTo("ul#flickr")
.wrap("<li><a href=\"" + item.link + "\"></a></li>");
if (i == 9) return false;
});
},
error: function(XHR, textStatus, errorThrown){
alert("ERREUR: " + textStatus);
alert("ERREUR: " + errorThrown);
}
});
});
Solution 3 - Jquery
A solution if you're stuck with jQuery 1.4:
var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
dataType: 'jsonp',
success: function() {
clearTimeout(id);
...
}
});
Solution 4 - Jquery
This may be a "known" limitation of jQuery; however, it does not seem to be well documented. I spent about 4 hours today trying to understand why my timeout was not working.
I switched to jquery.jsonp and it worked liked a charm. Thank you.
Solution 5 - Jquery
Seems to be resolved as of jQuery 1.5. I've tried the code above and I get the callback.
I say "seems to be" because the documentation of the error callback for jQuery.ajax() still has the following note:
> Note: This handler is not called for cross-domain script and JSONP requests.
Solution 6 - Jquery
The jquery-jsonp jQuery plugin mentioned in Jose Basilio's answer can now be found on GitHub.
Unfortunately the documentation is somewhat spartan, so I've provided a simple example:
$.jsonp({
cache: false,
url: "url",
callbackParameter: "callback",
data: { "key" : "value" },
success: function (json, textStatus, xOptions) {
// handle success - textStatus is "success"
},
error: function (xOptions, textStatus) {
// handle failure - textStatus is either "error" or "timeout"
}
});
Important Include the callbackParameter in the call $.jsonp() otherwise I've found that the callback function never gets injected into the query string of the service call (current as of version 2.4.0 of jquery-jsonp).
I know this question is old, but the issue of error handling using JSONP is still not 'resolved'. Hopefully this update will assist others as this question is the top-ranked within Google for "jquery jsonp error handling".
Solution 7 - Jquery
What about listening to the script's onerror event ? I had this problem and solved it by patching jquery's method where the script tag was created. Here is the interesting bit of code :
// Attach handlers for all browsers
script.onload = script.onreadystatechange = function( _, isAbort ) {
if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
cleanup();
// Callback if not abort
if ( !isAbort ) {
callback( 200, "success" );
}
}
};
/* ajax patch : */
script.onerror = function(){
cleanup();
// Sends an inappropriate error, still better than nothing
callback(404, "not found");
};
function cleanup(){
// Handle memory leak in IE
script.onload = script.onreadystatechange = null;
// Remove the script
if ( head && script.parentNode ) {
head.removeChild( script );
}
// Dereference the script
script = undefined;
}
What do you think of this solution ? Is it likely to cause compatibility issues ?