jQuery ajax (jsonp) ignoruje limit czasu i nie uruchamia zdarzenia błędu

89

Aby dodać trochę podstawowej obsługi błędów, chciałem przepisać fragment kodu, który wykorzystywał $ .getJSON jQuery do ściągnięcia niektórych zdjęć z Flickr. Powodem tego jest to, że $ .getJSON nie obsługuje błędów ani nie działa z limitami czasu.

Ponieważ $ .getJSON to tylko opakowanie wokół $ .ajax, postanowiłem przepisać to i zaskoczyć, działa bezbłędnie.

Teraz jednak zaczyna się zabawa. Kiedy celowo wywołuję błąd 404 (zmieniając adres URL) lub powoduję przekroczenie limitu czasu sieci (przez brak połączenia z interwebami), zdarzenie błędu w ogóle się nie uruchamia. Nie wiem, co robię źle. Pomoc jest bardzo ceniona.

Oto kod:

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

});

Dodam, że to pytanie zostało zadane, gdy jQuery był w wersji 1.4.2

Matijs
źródło

Odpowiedzi:

86

jQuery 1.5 i nowsze mają lepszą obsługę błędów w żądaniach JSONP. Musisz jednak użyć $.ajaxmetody zamiast $.getJSON. U mnie to działa:

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

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

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

Wydaje się, że przekroczenie limitu czasu załatwia sprawę i wywołuje procedurę obsługi błędów, gdy po 10 sekundach nie ma udanego żądania.

Napisałem również mały wpis na blogu na ten temat.

Ochrypły
źródło
23
Jestem teraz bardziej zirytowany, że waliłem głową w ten problem przez 2 dni i potrzeba trochę niejasnego wyszukiwania w StackOverflow, aby dowiedzieć się, że muszę dodać limit czasu do żądania międzydomenowego, aby spowodować uruchomienie błędu. Jak można tego nie wspomnieć w dokumentacji jQuery? Czy żądania jsonp między domenami są naprawdę takie rzadkie? W każdym razie dziękuję, dziękuję, dziękuję. +1
chrixian
W przypadku tego rozwiązania nie mogę uzyskać kodu stanu, zamiast tego wyświetlany jest komunikat „timeout”. Więc nie mogę wiedzieć, jaki jest rzeczywisty błąd i nie mogę sobie z nim poradzić.
Tarlog,
10
@Tarlog: to logiczne. Żądania JSONP nie są natywnymi żądaniami Ajax, są to po prostu <script>tagi JavaScript , które są wstawiane dynamicznie. W związku z tym jedyną rzeczą, którą możesz wiedzieć, jest to, że żądanie nie powiodło się, a nie jaki był kod stanu. Jeśli chcesz uzyskać kody statusu, musisz użyć tradycyjnych żądań Ajax lub innych technik międzydomenowych.
Husky,
1
Jak powiedział @Husky, nie możesz mieć kodów statusu w JSONP i uważam, że dlatego powinieneś starać się tego unikać. Jedynym sposobem uzyskania błędu jest ustawienie limitu czasu. Powinno to być lepiej wyjaśnione w dokumentacji jQuery (podobnie jak wiele innych rzeczy).
Alejandro García Iglesias,
67

Jest to znane ograniczenie natywnej implementacji jsonp w jQuery. Poniższy tekst pochodzi z IBM DeveloperWorks

JSONP to bardzo potężna technika tworzenia mashupów, ale niestety nie jest lekarstwem na wszystkie Twoje potrzeby w zakresie komunikacji międzydomenowej. Ma pewne wady, które należy wziąć pod uwagę przed przeznaczeniem środków na rozwój. Przede wszystkim nie ma obsługi błędów dla wywołań JSONP. Jeśli dynamiczne wstawianie skryptu zadziała, zostaniesz wezwany; jeśli nie, nic się nie dzieje. Po prostu cicho zawodzi. Na przykład nie możesz przechwycić błędu 404 z serwera. Nie możesz też anulować ani ponownie uruchomić żądania. Możesz jednak przekroczyć limit czasu po odczekaniu rozsądnego czasu. (Przyszłe wersje jQuery mogą mieć funkcję przerywania dla żądań JSONP).

Jednak w GoogleCode dostępna jest wtyczka jsonp, która zapewnia obsługę obsługi błędów. Aby rozpocząć, po prostu wprowadź następujące zmiany w kodzie.

Możesz go pobrać lub po prostu dodać odwołanie do skryptu do wtyczki.

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

Następnie zmodyfikuj wywołanie Ajax, jak pokazano poniżej:

$(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
źródło
Dzięki za odpowiedź José! Ograniczona implementacja natywnego jsonp w jQuery była powodem, dla którego zdecydowałem się zamiast tego na $ .ajax. Jednak wydaje się, że problemem nie jest to, że $ .getJSON jest opakowaniem wokół $ .ajax, ale typem danych: jsonp. Czy to jest poprawne?
Matijs
Do dzisiaj nie miałem czasu. Jednak to nie działa. Otrzymuję błąd, ale to wszystko. Nie wiem, jaki błąd, ponieważ errorThrown jest niezdefiniowany. Na razie zostanę przy $ .ajax i braku komunikatów o błędach. Javascript to dla mnie dodatkowa warstwa na górze strony. Jeśli Flickr nie działa lub wyświetla komunikaty o błędach, będziemy musieli na razie z tym pogodzić :) Dzięki za sugestię.
Matijs
Zasadniczo więc sugestia José dotycząca użycia wtyczki jsonp powinna, jeśli zostanie wykonana poprawnie, zadziałać. Na razie jednak pozostanę przy podstawowym opakowaniu json dla jQuery.
Matijs,
To zadziałało wspaniale dla mnie, gdy uderzyłem w ścianę z wbudowanymi ograniczeniami obsługi błędów jsonp
Jeremy Frey
7
Inny programista uratowany tą wskazówką. Dzięki!
chesterbr
12

Rozwiązanie, jeśli utkniesz z jQuery 1.4:

var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
    dataType: 'jsonp',
    success: function() {
        clearTimeout(id);
        ...
    }
});
Rob De Almeida
źródło
2
uwaga, jest to poprawne rozwiązanie dla ludzi, którzy utknęli w 1.4, ale ustawcie swoją zmienną timeout wystarczająco wysoko, żeby nie mieć problemów z powolnymi usługami sieciowymi :). jeśli ustawisz go na przykład na jedną sekundę, możesz zobaczyć, co mam na myśli, wywoływane jest wywołanie zwrotne błędu, podczas gdy po tej 1 sekundzie twoje połączenie jest nadal pobierane i wywołuje funkcję sukcesu. więc pamiętaj, aby ustawić go wystarczająco wysoko
Sander
@Sander funkcja sukcesu może zostać wywołana nawet, jeśli otrzymasz odpowiedź po 10 sekundach z limitem czasu 5 sekund. Wywołanie .abort()oczekującego jqXHR po upływie limitu czasu może być lepszym sposobem.
Matijs
8

Może to być „znane” ograniczenie jQuery; jednak wydaje się, że nie jest to dobrze udokumentowane. Spędziłem dziś około 4 godzin, próbując zrozumieć, dlaczego mój limit czasu nie działa.

Przerzuciłem się na jquery.jsonp i działało to przyjemnie. Dziękuję Ci.

Marc
źródło
2

Wydaje się, że został rozwiązany w jQuery 1.5. Wypróbowałem powyższy kod i otrzymałem oddzwonienie.

Mówię „wydaje się”, ponieważ dokumentacja wywołania zwrotnego błędu dla jQuery.ajax () nadal zawiera następującą notatkę:

Uwaga: ta procedura obsługi nie jest wywoływana dla skryptów międzydomenowych i żądań JSONP.

Peter Tate
źródło
1

JQuery jQuery plugin-jsonp wymienione w Jose Basilio za odpowiedź można teraz znaleźć na GitHub .

Niestety dokumentacja jest nieco spartańska, więc podałem prosty przykład:

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

Ważne Uwzględnij parametr callbackParameter w wywołaniu $ .jsonp (), w przeciwnym razie stwierdziłem, że funkcja callback nigdy nie zostanie wstrzyknięta do ciągu zapytania wywołania usługi (aktualna od wersji 2.4.0 jquery-jsonp).

Wiem, że to pytanie jest stare, ale problem obsługi błędów za pomocą JSONP nadal nie jest „rozwiązany”. Mamy nadzieję, że ta aktualizacja pomoże innym, ponieważ to pytanie zajmuje najwyższe miejsce w Google pod względem „obsługi błędów jquery jsonp”.

OiDatsMyLeg
źródło
niezłe znalezisko i niesamowite, że ten wątek wciąż żyje po trzech latach…
Matijs,
1

A co ze słuchaniem zdarzenia onerror w scenariuszu? Miałem ten problem i rozwiązałem go, łatając metodę jquery, w której został utworzony tag script. Oto interesujący fragment kodu:

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

Co myślisz o tym rozwiązaniu? Czy może to spowodować problemy ze zgodnością?

Hadrien Milano
źródło