jQuery ajax (jsonp) ignore un délai d'expiration et ne déclenche pas l'événement d'erreur

89

Pour ajouter une gestion des erreurs de base, je voulais réécrire un morceau de code qui utilisait $ .getJSON de jQuery pour extraire des photos de Flickr. La raison de cela est que $ .getJSON ne fournit pas de gestion des erreurs ni ne fonctionne avec les délais d'expiration.

Puisque $ .getJSON n'est qu'un wrapper autour de $ .ajax, j'ai décidé de réécrire la chose et de surprendre la surprise, cela fonctionne parfaitement.

Maintenant, le plaisir commence cependant. Lorsque je provoque délibérément un 404 (en modifiant l'URL) ou que le réseau expire (en n'étant pas connecté aux interwebs), l'événement d'erreur ne se déclenche pas du tout. Je ne sais pas ce que je fais de mal. L'aide est très appréciée.

Voici le 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);
        }
    });

});

Je voudrais ajouter que cette question a été posée lorsque jQuery était à la version 1.4.2

Matijs
la source

Réponses:

86

jQuery 1.5 et supérieur ont une meilleure prise en charge de la gestion des erreurs avec les requêtes JSONP. Cependant, vous devez utiliser la $.ajaxméthode au lieu de $.getJSON. Pour moi, cela fonctionne:

var req = $.ajax({
    url : url,
    dataType : "jsonp",
    timeout : 10000
});

req.success(function() {
    console.log('Yes! Success!');
});

req.error(function() {
    console.log('Oh noes!');
});

Le délai d'attente semble faire l'affaire et appeler le gestionnaire d'erreurs, lorsqu'il n'y a pas de demande réussie après 10 secondes.

J'ai également publié un petit article sur ce sujet.

Rauque
la source
23
Je suis plus qu'énervé en ce moment que je me suis cogné la tête contre ce problème pendant 2 jours et qu'il faut une recherche obscure sur StackOverflow pour savoir que je dois ajouter un délai à une demande inter-domaines pour que l'erreur se déclenche. Comment cela ne peut-il pas être mentionné dans la documentation jQuery? Les requêtes jsonp interdomaines sont-elles vraiment si rares? En tout cas, merci, merci, merci. +1
chrixian le
Avec cette solution, je ne peux pas obtenir le code d'état, j'obtiens à la place un "délai d'attente". Je ne peux donc pas savoir quelle est l'erreur réelle et je ne peux pas la gérer.
Tarlog le
10
@Tarlog: c'est logique. Les requêtes JSONP ne sont pas des requêtes Ajax natives, ce sont simplement des <script>balises Javascript insérées dynamiquement. La seule chose que vous pouvez donc savoir, c'est qu'une requête a échoué, pas quel était le code d'état. Si vous souhaitez obtenir des codes d'état, vous devez utiliser des requêtes Ajax traditionnelles ou d'autres techniques inter-domaines.
Husky le
1
Comme @Husky l'a dit, vous ne pouvez pas avoir de codes d'état avec JSONP, et je pense que c'est pourquoi vous devriez essayer de l'éviter. La seule façon d'obtenir une erreur est de mettre un timeout. Cela devrait être mieux expliqué dans la documentation jQuery (comme beaucoup d'autres choses).
Alejandro García Iglesias
67

Il s'agit d'une limitation connue de l'implémentation native de jsonp dans jQuery. Le texte ci-dessous provient d' IBM DeveloperWorks

JSONP est une technique très puissante pour créer des mashups, mais, malheureusement, ce n'est pas une panacée pour tous vos besoins de communication inter-domaines. Il présente certains inconvénients qui doivent être pris en considération avant d'engager des ressources de développement. Tout d'abord, il n'y a pas de gestion des erreurs pour les appels JSONP. Si l'insertion de script dynamique fonctionne, vous êtes appelé; sinon, rien ne se passe. Cela échoue silencieusement. Par exemple, vous ne parvenez pas à détecter une erreur 404 du serveur. Vous ne pouvez pas non plus annuler ou redémarrer la demande. Vous pouvez cependant expirer après avoir attendu un laps de temps raisonnable. (Les futures versions de jQuery peuvent avoir une fonctionnalité d'abandon pour les demandes JSONP.)

Cependant, il existe un plug-in jsonp disponible sur GoogleCode qui prend en charge la gestion des erreurs. Pour commencer, apportez simplement les modifications suivantes à votre code.

Vous pouvez soit le télécharger, soit simplement ajouter une référence de script au plug-in.

<script type="text/javascript" 
     src="http://jquery-jsonp.googlecode.com/files/jquery.jsonp-1.0.4.min.js">
</script>

Modifiez ensuite votre appel ajax comme indiqué ci-dessous:

$(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);
        }
    });
});
José Basilio
la source
Merci pour votre réponse José! L'implémentation limitée de jsonp natif dans jQuery était la raison pour laquelle j'ai opté pour $ .ajax à la place. Cependant, il semble alors que le problème n'est pas que $ .getJSON est un wrapper autour de $ .ajax mais le dataType: jsonp. Est-ce exact?
Matijs
Je n'ai pas eu le temps jusqu'à aujourd'hui. Cela ne fonctionne pas cependant. J'obtiens une erreur, mais c'est à peu près tout. Je ne sais pas quelle erreur comme errorThrown n'est pas définie. Pour l'instant, je m'en tiendrai à $ .ajax et à l'absence de messages d'erreur. Javascript est une couche supplémentaire au-dessus de la page Web pour moi. Si Flickr est en panne ou envoie des messages d'erreur, nous devrons simplement vivre avec pour l'instant :) Merci pour votre suggestion.
Matijs
Donc, fondamentalement, la suggestion de José utilisant le plugin jsonp devrait, si elle est faite correctement, fonctionner. Pour l'instant cependant, je m'en tiendrai au wrapper json de base pour jQuery.
Matijs
Cela a fonctionné à merveille pour moi une fois que j'ai frappé le mur avec les limitations intégrées de la gestion des erreurs de jsonp
Jeremy Frey
7
Un autre développeur sauvé par cette astuce. Merci!
chesterbr
12

Une solution si vous êtes coincé avec jQuery 1.4:

var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
    dataType: 'jsonp',
    success: function() {
        clearTimeout(id);
        ...
    }
});
Rob De Almeida
la source
2
juste une remarque, c'est une solution valable pour les personnes bloquées avec la version 1.4, mais définissez votre variable de délai d'expiration suffisamment élevée pour que vous n'ayez pas de problèmes avec les services Web lents :). si vous le définissez sur une seconde par exemple, vous pouvez voir ce que je veux dire, le rappel d'erreur est déclenché, tandis qu'après cette seconde, votre appel est toujours récupéré et appelle la fonction de succès. alors gardez à l'esprit de le définir suffisamment haut
Sander
@Sander la fonction de succès peut même être appelée si vous avez une réponse après 10 secondes avec un délai de 5 secondes. Appeler .abort()un jqXHR en attente après un délai d'expiration pourrait être un meilleur moyen.
Matijs
8

Cela peut être une limitation "connue" de jQuery; cependant, il ne semble pas bien documenté. J'ai passé environ 4 heures aujourd'hui à essayer de comprendre pourquoi mon délai d'attente ne fonctionnait pas.

Je suis passé à jquery.jsonp et cela a fonctionné comme un charme. Je vous remercie.

Marc
la source
2

Semble être résolu à partir de jQuery 1.5. J'ai essayé le code ci-dessus et je reçois le rappel.

Je dis "semble être" parce que la documentation du rappel d'erreur pour jQuery.ajax () contient toujours la note suivante:

Remarque: ce gestionnaire n'est pas appelé pour les requêtes de script inter-domaines et JSONP.

Peter Tate
la source
1

Le plugin jquery-jsonp jQuery mentionné dans la réponse de Jose Basilio peut maintenant être trouvé sur GitHub .

Malheureusement, la documentation est quelque peu spartiate, j'ai donc fourni un exemple simple:

    $.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 Incluez le paramètre callbackParameter dans l'appel $ .jsonp () sinon j'ai constaté que la fonction de rappel n'est jamais injectée dans la chaîne de requête de l'appel de service (en cours à partir de la version 2.4.0 de jquery-jsonp).

Je sais que cette question est ancienne, mais le problème de la gestion des erreurs à l'aide de JSONP n'est toujours pas «résolu». Espérons que cette mise à jour aidera les autres car cette question est la première de Google pour la "gestion des erreurs jquery jsonp".

OiDatsMyLeg
la source
belle trouvaille, et étonnant ce fil est toujours vivant après trois ans…
Matijs
1

Qu'en est-il d'écouter l'événement onerror du script? J'ai eu ce problème et je l'ai résolu en corrigeant la méthode de jquery où la balise script a été créée. Voici le morceau de code intéressant:

// 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;
}

Que pensez-vous de cette solution? Est-il susceptible de causer des problèmes de compatibilité?

Hadrien Milano
la source