Можно ли отменить отсрочку jQuery?

У меня есть ситуация, когда я хочу отменить отложенную. Отложенная связь связана с вызовом ajax.

Почему я использую отложенные

Я не использую обычные объекты xhr, возвращаемые $.ajax. Я использую jsonp, а это значит, что я не могу использовать коды состояния HTTP для обработки ошибок и должен встраивать их в ответы. Затем коды проверяются, и соответствующий отложенный объект помечен как разрешенный или отклоненный соответствующим образом. У меня есть пользовательская функция api, которая делает это для меня.

function api(options) {
  var url = settings('api') + options.url;
  var deferred = $.Deferred(function(){
    this.done(options.success);
    this.fail(options.error);
  });
  $.ajax({
    'url': url,
    'dataType':'jsonp',
    'data': (options.noAuth == true) ? options.data : $.extend(true, getAPICredentials(), options.data)
  }).success(function(jsonReturn){
    // Success
    if(hasStatus(jsonReturn, 'code', 200)) {
      deferred.resolveWith(this, [jsonReturn]);
    } 
    // Failure
    else {
      deferred.rejectWith(this, [jsonReturn]);
    }
  });

  return deferred;
}

Почему я хочу отменить отложенную

Существует поле ввода, которое служит фильтром для списка и автоматически обновляет список через полсекунды после ввода концов. Поскольку два вызова ajax могут быть выдающимися одновременно, мне нужно отменить предыдущий вызов, чтобы он не возвращался после второго и отображал старые данные.

Решения, которые мне не нравятся

  • Я не хочу отклонять отложенное, потому что это приведет к срабатыванию обработчиков с помощью .fail().
  • Я не могу игнорировать его, потому что он автоматически будет отмечен как разрешенный или отклоненный при возврате ajax.
  • Удаление отложенного приведет к ошибке при возврате вызова ajax и пытается пометить отложенное как разрешенное или отклоненное.

Что мне делать?

Есть ли способ отменить отложенные или удалить все прикрепленные обработчики?

Совет о том, как исправить мой дизайн, приветствуется, но предпочтение будет отдаваться поиску способа удаления обработчиков или предотвращения их стрельбы.

Ответ 1

Глядя в документ и код jQuery, я не вижу способа отменить jQuery отложенным.

Вместо этого вам, вероятно, нужен способ в вашем обработчике resolveWith, чтобы узнать, что последующий вызов ajax уже запущен, и этот вызов ajax должен игнорировать его результат. Вы можете сделать это с помощью счетчика с глобальным увеличением. В начале вашего вызова ajax вы увеличиваете счетчик, а затем получаете значение в локальной переменной или помещаете его как свойство в объект ajax. В вашем обработчике resolveWith вы проверяете, имеет ли счетчик все те же значения, что и при запуске вашего вызова ajax. Если нет, вы проигнорируете результат. Если это так, новых вызовов ajax не было, поэтому вы можете обработать результат.

В качестве альтернативы вы можете отказаться от запуска нового вызова ajax, когда вы уже в полете, поэтому у вас никогда не было более одного в полете за раз. Когда кто-то закончит, вы можете либо использовать этот результат, либо пристрелить следующий, если хотите.

Ответ 2

Пока вы не можете "отменить" отложенную, как вы хотите, вы можете создать простое закрытие, чтобы отслеживать последний вызов ajax через $.ajax, возвращающий объект jqXHR. Поступая таким образом, вы можете просто прекратить() вызов, когда новый jqXHR входит в игру, если последний не был завершен. В вашем случае кода он отклонит jqXHR и оставьте отложенное открытое для удаления, как вы хотели.

var api = (function() {
    var jqXHR = null;

    return function(options) {
        var url = options.url;

        if (jqXHR && jqXHR.state() === 'pending') {
            //Calls any error / fail callbacks of jqXHR
            jqXHR.abort();
        }

        var deferred = $.Deferred(function() {
            this.done(options.success);
            this.fail(options.error);
        });

        jqXHR = $.ajax({
             url: url,
             data: options.toSend,
             dataType: 'jsonp'
        });

        jqXHR.done(function(data, textStatus, jqXHR) {
            if (data.f && data.f !== "false") {
                deferred.resolve();
            } else {
                deferred.reject();
            }
        });

        //http://api.jquery.com/deferred.promise/  
        //keeps deferred state from being changed outside this scope      
        return deferred.promise();
    };
})();

Я разместил это на jsfiddle. Если вы хотите проверить это. Установка таймаута используется в сочетании с задержкой jsfiddles для имитации переадресации вызова. Для просмотра журналов вам понадобится браузер с поддержкой консоли.

На стороне примечания переключите любые методы .success(),.error() и complete() на отложенные методы done(), fail() и always(). Через jquery/ajax

Уведомление об изъятии: вызовы jqXHR.success(), jqXHR.error() и jqXHR.complete() будут устаревать в jQuery 1.8. Чтобы подготовить код для их возможного удаления, используйте jqXHR.done(), jqXHR.fail() и jqXHR.always() вместо более новых

Ответ 3

JustinY: похоже, вы уже близко к тому, что хотите. Вы уже используете две отложенные (внутренние → ajax и внешние → $.Deferred()). Затем вы используете внутреннее отложенное решение о том, как разрешить внешнюю отсрочку, основанную на некоторых условиях.

Ну, так что просто не разрешайте внешние отложенные вообще, когда вы этого не хотите (возможно, у вас есть логическая переменная, которая служит в качестве переключателя для разрешения внутреннего dfd для разрешения/отклонения вообще). Ничего плохого не произойдет: все обработчики, которые вы привязали ко всей этой функции, не будут срабатывать. Пример в вашей внутренней функции успеха:

if(gateOpen){
  gateOpen = false;
  if(hasStatus(jsonReturn, 'code', 200)) {
    deferred.resolveWith(this, [jsonReturn]);
  }
  else {
    deferred.rejectWith(this, [jsonReturn]);
  }
}

Некоторая другая логика в приложении решит, когда gateOpen вернется к истинному (некоторый тайм-аут _.throttle() или _.debounce(), взаимодействие с пользователем, что бы вы ни захотели). Если вы хотите отслеживать или отмените другие запросы в другом месте этой функции, вы тоже можете это сделать. Но основная вещь заключается в том, что вам не нужно разрешать ИЛИ отклонять внешний внешний вид. И это то же самое, что отменить его, даже если вы не отмените/не прервите внутренний.

Ответ 4

Я создал прокладку, которая легко добавляет возможность отменить отложенные объекты и запросы ajax.

Короче говоря, как только отложенный объект был отменен, разрешения/отклонения полностью игнорируются, а state становится "отмененным".

Согласно jQuery.com, "Как только объект вошел в разрешенное или отклоненное состояние, он остается в этом состоянии". Поэтому попытки отмены игнорируются, как только отложенный объект будет разрешен или отклонен.

(function () {
    originals = {
        deferred: $.Deferred,
        ajax: $.ajax
    };

    $.Deferred = function () {

        var dfr = originals.deferred(),
            cancel_dfr = originals.deferred();

        dfr.canceled = false;

        return {
            cancel: function () {
                if (dfr.state() == 'pending') {
                    dfr.canceled = true;
                    cancel_dfr.resolve.apply(this, arguments);
                }
                return this;
            },

            canceled: cancel_dfr.done,

            resolve: function () {
                if ( ! dfr.canceled) {
                    dfr.resolve.apply(dfr, arguments);
                    return this;
                }
            },

            resolveWith: function () {
                if ( ! dfr.canceled) {
                    dfr.resolveWith.apply(dfr, arguments);
                    return this;
                }
            },

            reject: function () {
                if ( ! dfr.canceled) {
                    dfr.reject.apply(dfr, arguments);
                    return this;
                }
            },

            rejectWith: function () {
                if ( ! dfr.canceled) {
                    dfr.rejectWith.apply(dfr, arguments);
                    return this;
                }
            },

            notify: function () {
                if ( ! dfr.canceled) {
                    dfr.notify.apply(dfr, arguments);
                    return this;
                }
            },

            notifyWith: function () {
                if ( ! dfr.canceled) {
                    dfr.notifyWith.apply(dfr, arguments);
                    return this;
                }
            },

            state: function () {
                if (dfr.canceled) {
                    return "canceled";
                } else {
                    return dfr.state();
                }
            },

            always   : dfr.always,
            then     : dfr.then,
            promise  : dfr.promise,
            pipe     : dfr.pipe,
            done     : dfr.done,
            fail     : dfr.fail,
            progress : dfr.progress
        };
    };


    $.ajax = function () {

        var dfr = $.Deferred(),
            ajax_call = originals.ajax.apply(this, arguments)
                .done(dfr.resolve)
                .fail(dfr.reject),

            newAjax = {},

            ajax_keys = [
                "getResponseHeader",
                "getAllResponseHeaders",
                "setRequestHeader",
                "overrideMimeType",
                "statusCode",
                "abort"
            ],

            dfr_keys = [
                "always",
                "pipe",
                "progress",
                "then",
                "cancel",
                "state",
                "fail",
                "promise",
                "done",
                "canceled"
            ];

        _.forEach(ajax_keys, function (key) {
            newAjax[key] = ajax_call[key];
        });

        _.forEach(dfr_keys, function (key) {
            newAjax[key] = dfr[key];
        });

        newAjax.success = dfr.done;
        newAjax.error = dfr.fail;
        newAjax.complete = dfr.always;

        Object.defineProperty(newAjax, 'readyState', {
            enumerable: true,
            get: function () {
                return ajax_call.readyState;
            },
            set: function (val) {
                ajax_call.readyState = val;
            }
        });

        Object.defineProperty(newAjax, 'status', {
            enumerable: true,
            get: function () {
                return ajax_call.status;
            },
            set: function (val) {
                ajax_call.status = val;
            }
        });

        Object.defineProperty(newAjax, 'statusText', {
            enumerable: true,
            get: function () {
                return ajax_call.statusText;
            },
            set: function (val) {
                ajax_call.statusText = val;
            }
        });

        // canceling an ajax request should also abort the call
        newAjax.canceled(ajax_call.abort);

        return newAjax;
    };
});

После добавления вы можете отменить вызов ajax:

var a = $.ajax({
        url: '//example.com/service/'
    });

a.cancel('the request was canceled');

// Now, any resolutions or rejections are ignored, and the network request is dropped.

.. или простой отложенный объект:

var dfr = $.Deferred();

dfr
    .done(function () {
        console.log('Done!');
    })
    .fail(function () {
        console.log('Nope!');
    });

dfr.cancel(); // Now, the lines below are ignored. No console logs will appear.

dfr.resolve();
dfr.reject();