jQuery ajax (jsonp) ignora un tiempo de espera y no activa el evento de error

89

Para agregar un manejo básico de errores, quería reescribir un fragmento de código que usaba $ .getJSON de jQuery para extraer algunas fotos de Flickr. La razón para hacer esto es que $ .getJSON no proporciona manejo de errores ni trabaja con tiempos de espera.

Como $ .getJSON es solo un envoltorio alrededor de $ .ajax, decidí reescribirlo y, sorpresa sorpresa, funciona perfectamente.

Ahora empieza la diversión. Cuando deliberadamente provoco un 404 (al cambiar la URL) o hago que la red se agote (al no estar conectado a las redes), el evento de error no se activa en absoluto. No sé qué estoy haciendo mal. Se agradece mucho la ayuda.

Aquí está el código:

$(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);
        }
    });

});

Me gustaría agregar que esta pregunta se hizo cuando jQuery estaba en la versión 1.4.2

Matijs
fuente

Respuestas:

86

jQuery 1.5 y versiones posteriores tienen un mejor soporte para el manejo de errores con solicitudes JSONP. Sin embargo, debe utilizar el $.ajaxmétodo en lugar de $.getJSON. Para mí, esto funciona:

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

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

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

El tiempo de espera parece funcionar y llama al controlador de errores, cuando no hay una solicitud exitosa después de 10 segundos.

También hice una pequeña entrada de blog sobre este tema.

Fornido
fuente
23
Estoy más que molesto en este momento porque me he golpeado de cabeza contra este problema durante 2 días y se necesita una búsqueda oscura en StackOverflow para descubrir que tengo que agregar un tiempo de espera a una solicitud entre dominios para que se active el error. ¿Cómo no se puede mencionar esto en la documentación de jQuery? ¿Son las solicitudes jsonp entre dominios realmente tan poco comunes? En cualquier caso, gracias, gracias, gracias. +1
chrixian
Con esta solución, no puedo obtener el código de estado, sino que obtengo "tiempo de espera". Entonces, no puedo saber cuál es el error real y no puedo manejar su propiedad.
Tarlog
10
@Tarlog: eso es lógico. Las solicitudes JSONP no son solicitudes Ajax nativas, son simplemente <script>etiquetas Javascript que se insertan dinámicamente. Por lo tanto, lo único que puede saber es que una solicitud falló, no cuál era el código de estado. Si desea obtener códigos de estado, debe utilizar solicitudes Ajax tradicionales u otras técnicas de dominio cruzado.
Husky
1
Como dijo @Husky, no puede tener códigos de estado con JSONP, y creo que es por eso que debe intentar evitarlo. La única forma de obtener un error es poner un tiempo de espera. Esto debería explicarse mejor en la documentación de jQuery (como muchas otras cosas).
Alejandro García Iglesias
67

Esta es una limitación conocida con la implementación nativa de jsonp en jQuery. El texto a continuación es de IBM DeveloperWorks

JSONP es una técnica muy poderosa para crear mashups, pero, desafortunadamente, no es una panacea para todas sus necesidades de comunicación entre dominios. Tiene algunos inconvenientes que deben tenerse en cuenta antes de comprometer recursos para el desarrollo. En primer lugar, no hay manejo de errores para las llamadas JSONP. Si la inserción dinámica del script funciona, te llamarán; si no, no pasa nada. Simplemente falla en silencio. Por ejemplo, no puede detectar un error 404 del servidor. Tampoco puede cancelar o reiniciar la solicitud. Sin embargo, puede esperar un tiempo de espera después de esperar un período de tiempo razonable. (Las versiones futuras de jQuery pueden tener una función de cancelación para solicitudes JSONP).

Sin embargo, hay un complemento jsonp disponible en GoogleCode que brinda soporte para el manejo de errores. Para comenzar, simplemente realice los siguientes cambios en su código.

Puede descargarlo o simplemente agregar una referencia de script al complemento.

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

Luego modifique su llamada ajax como se muestra a continuación:

$(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
fuente
¡Gracias por tu respuesta José! La implementación limitada de jsonp nativo en jQuery fue la razón por la que opté por $ .ajax. Sin embargo, parece que el problema no es que $ .getJSON sea un contenedor de $ .ajax sino que el tipo de datos: jsonp. ¿Es eso correcto?
Matijs
No he tenido tiempo hasta hoy. Sin embargo, no funciona. Recibo un error, pero eso es todo. No sé qué error como errorThrown no está definido. Por ahora me quedaré con $ .ajax y la falta de mensajes de error. Javascript es una capa adicional en la parte superior de la página web para mí. Si Flickr está inactivo o arroja mensajes de error, tendremos que aceptarlo por ahora :) Gracias por tu sugerencia.
Matijs
Entonces, básicamente, la sugerencia de José de usar el complemento jsonp debería funcionar, si se hace correctamente. Por ahora, sin embargo, me quedaré con el contenedor json básico para jQuery.
Matijs
Esto funcionó de manera brillante para mí una vez que golpeé la pared con las limitaciones integradas del manejo de errores de jsonp
Jeremy Frey
7
Otro desarrollador salvado por este consejo. ¡Gracias!
Chesterbr
12

Una solución si está atascado con jQuery 1.4:

var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
    dataType: 'jsonp',
    success: function() {
        clearTimeout(id);
        ...
    }
});
Rob De Almeida
fuente
2
solo un comentario, es una solución válida para las personas atascadas con 1.4, pero establezca su variable de tiempo de espera lo suficientemente alta, para que no tenga problemas con servicios web lentos :) Si lo configura en un segundo, por ejemplo, puede ver lo que quiero decir, se activa la devolución de llamada de error, mientras que después de ese segundo su llamada aún se recupera y llama a la función de éxito. así que tenga en cuenta que debe establecerlo razonablemente alto
Sander
@Sander, la función de éxito incluso podría llamarse si obtuviera una respuesta después de 10 segundos con un tiempo de espera de 5 segundos. Llamar .abort()a un jqXHR pendiente después de un período de tiempo de espera podría ser una mejor manera.
Matijs
8

Esta puede ser una limitación "conocida" de jQuery; sin embargo, no parece estar bien documentado. Pasé aproximadamente 4 horas hoy tratando de entender por qué mi tiempo de espera no estaba funcionando.

Cambié a jquery.jsonp y funcionó como un encanto. Gracias.

Bagazo
fuente
2

Parece estar resuelto a partir de jQuery 1.5. Probé el código anterior y recibo la devolución de llamada.

Digo "parece ser" porque la documentación de la devolución de llamada de error para jQuery.ajax () todavía tiene la siguiente nota:

Nota: Este controlador no se llama para solicitudes JSONP ni secuencias de comandos entre dominios.

Peter Tate
fuente
1

El complemento jquery-jsonp jQuery mencionado en la respuesta de Jose Basilio ahora se puede encontrar en GitHub .

Desafortunadamente, la documentación es algo espartana, por lo que he proporcionado un ejemplo 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"
        }
    });

Importante Incluya el parámetro callbackParameter en la llamada $ .jsonp () de lo contrario, descubrí que la función de devolución de llamada nunca se inyecta en la cadena de consulta de la llamada de servicio (actual a partir de la versión 2.4.0 de jquery-jsonp).

Sé que esta pregunta es antigua, pero el problema del manejo de errores usando JSONP aún no está 'resuelto'. Con suerte, esta actualización ayudará a otros, ya que esta pregunta es la mejor clasificada dentro de Google para "manejo de errores jquery jsonp".

OiDatsMyLeg
fuente
Buen hallazgo, y sorprendente, este hilo sigue vivo después de tres años…
Matijs
1

¿Qué hay de escuchar el evento de error del guión? Tuve este problema y lo resolví parcheando el método de jquery donde se creó la etiqueta de script. Aquí está el código interesante:

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

¿Qué opinas de esta solución? ¿Es probable que cause problemas de compatibilidad?

Hadrien Milano
fuente