jQuery ajax (jsonp) ignora um tempo limite e não dispara o evento de erro

89

Para adicionar algum tratamento básico de erro, eu queria reescrever um trecho de código que usava $ .getJSON do jQuery para puxar algumas fotos do Flickr. O motivo para fazer isso é que $ .getJSON não fornece tratamento de erros ou trabalha com tempos limite.

Já que $ .getJSON é apenas um invólucro em torno de $ .ajax, decidi reescrever a coisa e surpresa surpresa, funciona perfeitamente.

Agora a diversão começa. Quando eu deliberadamente causo um 404 (alterando a URL) ou faço a rede atingir o tempo limite (por não estar conectada às interwebs), o evento de erro não dispara. Não sei o que estou fazendo de errado. A ajuda é muito apreciada.

Aqui está o 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);
        }
    });

});

Eu gostaria de acrescentar que esta pergunta foi feita quando o jQuery estava na versão 1.4.2

Matijs
fonte

Respostas:

86

jQuery 1.5 e superior têm melhor suporte para tratamento de erros com solicitações JSONP. No entanto, você precisa usar o $.ajaxmétodo em vez de $.getJSON. Para mim, isso funciona:

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

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

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

O tempo limite parece funcionar e chamar o manipulador de erros, quando não houver solicitação bem-sucedida após 10 segundos.

Eu fiz uma pequena postagem no blog sobre esse assunto também.

Husky
fonte
23
Estou muito chateado agora por ter batido de cabeça contra esse problema por 2 dias e leva alguma pesquisa obscura no StackOverflow para descobrir que tenho que adicionar um tempo limite a uma solicitação de domínio cruzado para que o erro seja disparado. Como isso não pode ser mencionado na documentação do jQuery? As solicitações de cross-domain jsonp são realmente incomuns? Em qualquer caso, obrigado, obrigado, obrigado. +1
chrixian
Com esta solução, não consigo obter o código de status, mas sim "tempo limite". Portanto, não posso saber qual é o erro real e não posso lidar com ele propriedade.
Tarlog de
10
@Tarlog: isso é lógico. As solicitações JSONP não são solicitações Ajax nativas, são simplesmente <script>tags Javascript inseridas dinamicamente. Portanto, a única coisa que você pode saber é que uma solicitação falhou, não qual era o código de status. Se quiser obter códigos de status, você precisa usar solicitações Ajax tradicionais ou outras técnicas de domínio cruzado.
Husky
1
Como @Husky disse, você não pode ter códigos de status com JSONP, e acredito que é por isso que você deve tentar evitá-los. A única maneira de obter um erro é colocando um tempo limite. Isso deve ser melhor explicado na documentação do jQuery (como muitas outras coisas).
Alejandro García Iglesias
67

Esta é uma limitação conhecida com a implementação jsonp nativa em jQuery. O texto abaixo é do IBM DeveloperWorks

JSONP é uma técnica muito poderosa para construir mashups, mas, infelizmente, não é a cura para todas as suas necessidades de comunicação entre domínios. Ele tem algumas desvantagens que devem ser levadas em consideração antes de comprometer os recursos de desenvolvimento. Em primeiro lugar, não há tratamento de erros para chamadas JSONP. Se a inserção de script dinâmico funcionar, você será chamado; se não, nada acontece. Ele simplesmente falha silenciosamente. Por exemplo, você não consegue detectar um erro 404 do servidor. Nem você pode cancelar ou reiniciar o pedido. Você pode, no entanto, atingir o tempo limite após aguardar um período de tempo razoável. (As versões futuras do jQuery podem ter um recurso de anulação para solicitações JSONP.)

No entanto, há um plug-in jsonp disponível no GoogleCode que fornece suporte para tratamento de erros. Para começar, basta fazer as seguintes alterações em seu código.

Você pode baixá-lo ou apenas adicionar uma referência de script ao plug-in.

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

Em seguida, modifique sua chamada ajax conforme mostrado abaixo:

$(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
fonte
Obrigado por responder José! A implementação limitada de jsonp nativo em jQuery foi o motivo pelo qual optei por $ .ajax. No entanto, parece então que o problema não é que $ .getJSON é um invólucro em torno de $ .ajax, mas sim o dataType: jsonp. Isso está correto?
Matijs
Não tive tempo até hoje. No entanto, não funciona. Estou recebendo um erro, mas é só isso. Não sei qual é o erro, pois errorThrown é indefinido. Por enquanto, vou ficar com $ .ajax e a falta de mensagens de erro. Javascript é uma camada extra no topo da página da web para mim. Se o Flickr estiver fora do ar ou exibindo mensagens de erro, teremos que conviver com isso por enquanto :) Obrigado por sua sugestão.
Matijs
Basicamente, a sugestão do José de usar o plugin jsonp deve funcionar corretamente. Por enquanto, no entanto, vou ficar com o wrapper json básico para jQuery.
Matijs
Isso funcionou brilhantemente para mim, uma vez que eu tinha atingido a parede com as limitações embutidas de tratamento de erros do jsonp
Jeremy Frey,
7
Outro desenvolvedor salvo por esta dica. Obrigado!
chesterbr
12

Uma solução se você estiver preso ao jQuery 1.4:

var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
    dataType: 'jsonp',
    success: function() {
        clearTimeout(id);
        ...
    }
});
Rob De Almeida
fonte
2
apenas uma observação, é uma solução válida para pessoas presas ao 1.4, mas defina sua variável de tempo limite alto o suficiente, para que você não tenha problemas com webservices lentos :). se você configurá-lo para um segundo, por exemplo, você pode ver o que quero dizer, o retorno de chamada de erro é acionado, enquanto depois desse 1 segundo sua chamada ainda é obtida e chama a função de sucesso. Portanto, lembre-se de defini-lo razoavelmente alto o suficiente
Sander
@Sander, a função de sucesso pode até ser chamada se você obtiver uma resposta após 10 segundos com um tempo limite de 5 segundos. Chamar .abort()um jqXHR pendente após um período de tempo limite pode ser uma maneira melhor.
Matijs
8

Esta pode ser uma limitação "conhecida" do jQuery; entretanto, não parece estar bem documentado. Passei cerca de 4 horas hoje tentando entender por que meu tempo limite não estava funcionando.

Mudei para jquery.jsonp e funcionou como um encanto. Obrigado.

Marc
fonte
2

Parece estar resolvido a partir do jQuery 1.5. Tentei o código acima e recebo o retorno de chamada.

Digo "parece ser" porque a documentação do retorno de chamada de erro para jQuery.ajax () ainda tem a seguinte observação:

Nota: Este manipulador não é chamado para script de domínio cruzado e solicitações JSONP.

Peter Tate
fonte
1

O plugin jquery-jsonp jQuery mencionado na resposta de Jose Basilio agora pode ser encontrado no GitHub .

Infelizmente, a documentação é um tanto espartana, então forneci um exemplo simples:

    $.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 Inclua o callbackParameter na chamada $ .jsonp (), caso contrário, descobri que a função de retorno de chamada nunca é injetada na string de consulta da chamada de serviço (atual a partir da versão 2.4.0 de jquery-jsonp).

Eu sei que esta questão é antiga, mas o problema de tratamento de erros usando JSONP ainda não foi 'resolvido'. Esperamos que esta atualização ajude outras pessoas, pois esta questão é a melhor classificada no Google em "tratamento de erros jquery jsonp".

OiDatsMyLeg
fonte
bom achado e incrível, este tópico ainda está vivo depois de três anos ...
Matijs
1

Que tal ouvir o evento onerror do script? Eu tive esse problema e resolvi corrigindo o método jquery onde a tag de script foi criada. Aqui está a parte interessante do código:

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

O que você acha desta solução? É provável que cause problemas de compatibilidade?

Hadrien Milano
fonte