89

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

Matijs
  • 3,118
  • 2
  • 29
  • 41

7 Answers7

86

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.

Husky
  • 5,757
  • 2
  • 46
  • 41
  • 23
    I'm beyond annoyed right now that I've banged by head against this problem for 2 days and it takes some obscure search on StackOverflow to find out I have to add a timeout to a cross-domain request to get the error to fire. How can this not be mentioned in the jQuery documentation? Is cross-domain jsonp requests really that uncommon? In any case, thank you, thank you, thank you. +1 – chrixian Jul 04 '11 at 19:28
  • With this solution I'm cannot get the status code, I get "timeout" instead. So I cannot know what is the actual error, and cannot handle it property. – Tarlog Jul 14 '11 at 08:52
  • 10
    @Tarlog: that's logical. JSONP requests are not native Ajax requests, they're simply Javascript ` – Husky Jul 14 '11 at 14:10
  • 1
    As @Husky said, you can't have status codes with JSONP, and I believe that's why you should try to avoid it. The only way of getting a error is putting a timeout. This should be better explained in jQuery documentation (as many other things). – Alejandro García Iglesias Feb 07 '13 at 23:38
67

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);
        }
    });
});
Jose Basilio
  • 50,714
  • 13
  • 121
  • 117
  • Thanks for you answer José! The limited implementation of native jsonp in jQuery was the reason I went for $.ajax instead. However, it seems then that the problem isn't that $.getJSON is a wrapper around $.ajax but the dataType: jsonp. Is that correct? – Matijs Jun 16 '09 at 17:44
  • Haven't had time until today. It doesn't work however. I'm getting an error, but that's about it. I don't know what error as errorThrown is undefined. For now I'll stick with $.ajax and the lack of error messages. Javascript is an extra layer on top of the webpage for me. If Flickr is down or throwing error messages, we'll just have to live with it for now :) Thanks for your suggestion. – Matijs Jun 22 '09 at 09:22
  • So basically José's suggestion using the jsonp plugin should, if done correctly, work. For now however I'll stick with the basic json wrapper for jQuery. – Matijs Jul 23 '09 at 10:20
  • This worked brilliantly for me once I had hit the wall with the built-in limitations of jsonp's error handling – Jeremy Frey Oct 16 '09 at 18:23
  • 7
    Another developer saved by this tip. Thanks! – chesterbr Nov 07 '09 at 03:37
  • any chance of such a jsonp plugin working in zepto? i usually work with jQuery but might most likely have to work with zepto in a future project. and i know zepto is less equipped on the ajax front. and has the same issues with jsonp not triggering errors. – Sander Feb 28 '12 at 15:23
12

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);
        ...
    }
});
Beto Dealmeida
  • 564
  • 3
  • 8
  • 2
    just a remark, its a valid solution for people stuck with 1.4, but set your timeout variable high enough, so you don't have problems with slow webservices :). if you set it to one second for example, you can see what i mean, the error callback gets triggered, while after that 1 second your call still gets fetched and calls the success function. so just keep in mind to set it reasonably high enough – Sander Feb 28 '12 at 15:21
  • @Sander the success function could even be called if you got an answer after 10 seconds with a timeout of 5 seconds. Calling `.abort()` on a pending jqXHR after a timeout period might be a better way. – Matijs Jun 08 '16 at 09:45
8

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.

gregmac
  • 24,276
  • 10
  • 87
  • 118
Marc
  • 91
  • 1
  • 1
2

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.

Peter Tate
  • 2,178
  • 1
  • 21
  • 32
1

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".

Community
  • 1
  • 1
1

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 ?

Hadrien Milano
  • 111
  • 1
  • 3