jQuery ajax (jsonp) игнорирует тайм-аут и не запускает событие ошибки

89

Чтобы добавить базовую обработку ошибок, я хотел переписать фрагмент кода, который использовал jQuery $ .getJSON для извлечения некоторых фотографий с Flickr. Причина в том, что $ .getJSON не поддерживает обработку ошибок и не работает с тайм-аутами.

Поскольку $ .getJSON - это просто оболочка вокруг $ .ajax, я решил переписать эту штуку и удивить сюрприз, она работает безупречно.

Но теперь начинается самое интересное. Когда я намеренно вызываю 404 (путем изменения URL-адреса) или вызываю тайм-аут сети (не будучи подключенным к межсетевым соединениям), событие ошибки вообще не срабатывает. Я не понимаю, что делаю не так. Помощь очень ценится.

Вот код:

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

});

Хочу добавить, что этот вопрос был задан, когда jQuery был на версии 1.4.2.

Матийс
источник

Ответы:

86

jQuery 1.5 и выше лучше поддерживают обработку ошибок с помощью запросов JSONP. Однако вам нужно использовать $.ajaxметод вместо $.getJSON. Для меня это работает:

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

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

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

Тайм-аут, кажется, помогает и вызывает обработчик ошибок, если в течение 10 секунд нет успешного запроса.

Я также сделал небольшой пост в блоге на эту тему.

Хаски
источник
23
Меня сейчас очень раздражает то, что я бился головой об этой проблеме в течение 2 дней, и требуется некоторый непонятный поиск в StackOverflow, чтобы узнать, что мне нужно добавить тайм-аут к междоменному запросу, чтобы сработала ошибка. Как можно не упомянуть об этом в документации jQuery? Неужели междоменные запросы jsonp настолько редки? В любом случае спасибо, спасибо, спасибо. +1
chrixian 04
С помощью этого решения я не могу получить код состояния, вместо этого я получаю «тайм-аут». Поэтому я не могу знать, в чем заключается настоящая ошибка, и не могу обработать ее свойство.
Tarlog
10
@Tarlog: это логично. Запросы JSONP не являются собственными запросами Ajax, это просто <script>теги Javascript , которые вставляются динамически. Таким образом, вы можете знать только то, что запрос завершился неудачно, а не код состояния. Если вы хотите получить коды состояния, вам необходимо использовать традиционные запросы Ajax или другие междоменные методы.
Husky
1
Как сказал @Husky, у вас не может быть кодов состояния с JSONP, и я считаю, что вам следует избегать этого. Единственный способ получить ошибку - установить тайм-аут. Это должно быть лучше объяснено в документации jQuery (как и многое другое).
Алехандро Гарсиа Иглесиас
67

Это известное ограничение встроенной реализации jsonp в jQuery. Текст ниже взят из IBM DeveloperWorks

JSONP - очень мощный метод создания гибридных приложений, но, к сожалению, он не является панацеей от всех ваших потребностей в междоменном взаимодействии. У него есть некоторые недостатки, которые необходимо серьезно рассмотреть, прежде чем выделять ресурсы на разработку. Прежде всего, нет обработки ошибок для вызовов JSONP. Если динамическая вставка скрипта работает, вам позвонят; если нет, ничего не происходит. Он просто тихо терпит неудачу. Например, вы не можете поймать ошибку 404 с сервера. Вы также не можете отменить или перезапустить запрос. Однако вы можете установить тайм-аут после разумного ожидания. (В будущих версиях jQuery может быть функция прерывания для запросов JSONP.)

Однако в GoogleCode доступен плагин jsonp, который обеспечивает поддержку обработки ошибок. Для начала просто внесите в свой код следующие изменения.

Вы можете либо скачать его, либо просто добавить ссылку на скрипт в плагин.

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

Затем измените свой вызов ajax, как показано ниже:

$(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);
        }
    });
});
Хосе Базилио
источник
Спасибо за ответ, Хосе! Ограниченная реализация собственного jsonp в jQuery была причиной, по которой я выбрал $ .ajax. Однако, похоже, проблема не в том, что $ .getJSON является оболочкой вокруг $ .ajax, а в dataType: jsonp. Это верно?
Matijs
До сегодняшнего дня не было времени. Однако это не работает. Я получаю сообщение об ошибке, но это все. Я не знаю, какая ошибка errorThrown не определена. Пока я буду придерживаться $ .ajax и отсутствия сообщений об ошибках. Для меня Javascript - это дополнительный слой поверх веб-страницы. Если Flickr не работает или выдает сообщения об ошибках, нам пока придется смириться с этим :) Спасибо за ваше предложение.
Matijs
Так что в основном предложение Хосе об использовании плагина jsonp должно, если все сделано правильно, работать. Однако пока я буду придерживаться базовой json-оболочки для jQuery.
Matijs
Это сработало для меня блестяще, когда я столкнулся с проблемой встроенных ограничений обработки ошибок jsonp
Джереми Фрей
7
Другой разработчик спас эту подсказку. Благодарность!
chesterbr
12

Решение, если вы застряли на jQuery 1.4:

var timeout = 10000;
var id = setTimeout( errorCallback, timeout );
$.ajax({
    dataType: 'jsonp',
    success: function() {
        clearTimeout(id);
        ...
    }
});
Роб Де Алмейда
источник
2
просто замечание, это действительное решение для людей, застрявших с 1.4, но установите достаточно высокую переменную тайм-аута, чтобы у вас не было проблем с медленными веб-сервисами :). если вы установите его, например, на одну секунду, вы можете увидеть, что я имею в виду, срабатывает обратный вызов ошибки, а после этой 1 секунды ваш вызов все еще выбирается и вызывает функцию успеха. так что просто не забывайте устанавливать его достаточно высоко
Сандер
@Sander функцию успеха можно было бы даже вызвать, если вы получили ответ через 10 секунд с таймаутом в 5 секунд. Вызов .abort()ожидающего jqXHR после периода ожидания может быть лучшим способом.
Matijs
8

Это может быть «известное» ограничение jQuery; однако, похоже, это плохо документировано. Сегодня я потратил около 4 часов, пытаясь понять, почему мой тайм-аут не работает.

Я переключился на jquery.jsonp, и он работал как шарм. Спасибо.

Марк
источник
2

Кажется, решено с jQuery 1.5. Я пробовал приведенный выше код и получил обратный вызов.

Я говорю «похоже», потому что в документации обратного вызова ошибки для jQuery.ajax () все еще есть следующее примечание:

Примечание. Этот обработчик не вызывается для междоменного скрипта и запросов JSONP.

Питер Тейт
источник
1

Плагин jquery-jsonp jQuery, упомянутый в ответе Хосе Базилио, теперь можно найти на GitHub .

К сожалению, документация несколько спартанская, поэтому я привел простой пример:

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

Важно. Включите параметр callbackParameter в вызов $ .jsonp (), иначе я обнаружил, что функция обратного вызова никогда не вставляется в строку запроса вызова службы (текущая с версии 2.4.0 jquery-jsonp).

Я знаю, что это старый вопрос, но проблема обработки ошибок с использованием JSONP до сих пор не решена. Надеюсь, это обновление поможет другим, так как этот вопрос занимает первое место в Google по "обработке ошибок jquery jsonp".

OiDatsMyLeg
источник
хорошая находка, и удивительно, что эта ветка все еще жива спустя три года…
Matijs
1

А как насчет прослушивания события onerror сценария? У меня была эта проблема, и я решил ее, исправив метод jquery, в котором был создан тег script. Вот интересный фрагмент кода:

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

Что вы думаете об этом решении? Может ли это вызвать проблемы с совместимостью?

Адриен Милано
источник