Как вы прикрепляете обратные вызовы к отложенному объекту, который прикреплен к отложенному объекту?

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

Существуют некоторые варианты того, как формируются эти цепочки запросов, поэтому я надеялся собрать запросы с использованием объектов jQuery Deferred.

Я вижу, как я могу связать второй запрос с первым, но я не вижу, как я могу связать третий запрос со вторым.

function Step1() { return $.ajax(<foo>);}
function Step2() { return $.ajax(<foo>);}
function Step3() { return $.ajax(<foo>);}

$(function() {
   Step1().then(Step2).then(Step3);
});

Намерение состоит в том, что Step3 запускается, когда Step2 разрешен, но отложенный объект возвращается .then(Step2) от Step1, поэтому Step3 добавляется в качестве обратного вызова для Step1.

Я думаю, что яснее, что я пытаюсь сделать, если вы посмотрите этот пример jsFiddle.Изменить: Вот тот же сценарий с задержкой, добавленной ко второму вызову, чтобы сделать его более очевидным.

3 ответа

Решение

Нам повезло, и мы имели некоторую гибкость в графике. Мы закончили тем, что использовали .pipe() добавление цепочек к отложенным объектам в jQuery 1.6.

Спасибо всем за помощь!

$(function() {
    $.when(Step1).then(function() {
         $.when(Step2).then(Step3);
    });
});

Для обработки ошибок я рекомендую переписать Stepn:

function Stepn() { 
    return $.ajax(<foo>).fail(function() {
         // handle failure
    });
}

Использование обратных вызовов в этом формате позволяет вам делать то, что вы хотите. если у вас более 5 шагов, отступ становится беспорядочным, и для этого может быть полезно построить очередь.

Вот живой пример

var Queue = function() {
    var q = [];
    var that = this;

    // If items in queue then run them.
    function moveNext() {
        if (q.length > 0) {
            that.runItem();
        }
    }

    // run first item in queue
    this.runItem = function() {
        // get item
        var item = q.shift();
        // when deferred object then run then ...
        $.when(item.item).then([item.options.done, function() {
            // item finished, move to next.
            moveNext();
        }], [item.options.fail, function() {
            // if run item always then move next on failure.
            if (item.options.always) {
                moveNext();
            }
        }]);
    };

    this.add = function(def, options) {
        // if array then call add on each item in array
        if ($.isArray(def)) {
            for (var d in def) {
                this.add(d, options);
            }
            // return as we are done.
            return this;
        }
        // push item onto array
        q.push({
            item: def,
            options: options
        });
        // if items & not delay then run item.
        if (q.length === 1 && !options.delay) {
            this.runItem();
        }
        // enable jQuery style chaining \o/
        return this;
    };
};

Queue.add([def, def, ...], options) Добавляет отложенный элемент или массив отложенных элементов в очередь. Может использоваться с одним отложенным элементом или массивом. Карта параметров выглядит следующим образом

{
    "delay" : Boolean, // if true do not run the item in the queue after appending it.
    "done" : Function, // optional done call back
    "fail" : Function, // optional fail call back
    "always": Boolean // if true run the next item in the queue even if this item fails.
}

Queue.runItem, функция, которая запускает следующий элемент в очереди. Вызывается изнутри, можно использовать вручную в сочетании со свойством delay.

Я начал бороться с этим недавно (см. Мой вопрос здесь), вдохновленный серией блогов Джеймсом Когланом.

Поработав некоторое время с "монадами", я вернулся к желанию, чтобы можно было "связывать" отложенные объекты вместе. Проблема в том, что "done" возвращает тот же отложенный объект, а не новый.

Некоторое время я просматривал код jquery и понял, что никак не могу внедрить что-либо в код Deferred или _Deferred. Тем не менее, можно ввести наш собственный объект в качестве параметра promise() функция. Так что, если мы создадим функцию, которая будет генерировать цепное обещание для нас...

var Chainable = function Chainable() {
     return {
         chain : function(next) { //next: another function which returns Deferred
                 var newDef = $.Deferred(); //we resolve this when next is done
                 //next line: call next with (a||null) for method-tolerance
                 this.done(function(a) { next(a||null).done(newDef.resolve); });
                 return newDef.promise(Chainable());
         }
     };
 }

... затем мы можем использовать его для улучшения нашего стиля:

var asyncMessage = function(msg) {
    var dfd = new jQuery.Deferred();
    setTimeout(function() { dfd.resolve(msg); }, 1000);
    return dfd.promise(Chainable());
};

asyncMessage("Chained:A")
     .chain(function(m) { return asyncMessage(m + "B"); })
     .chain(function(m) { return asyncMessage(m + "C"); })
     .done(log); // -> outputs "ABC"

Посмотрите jsfiddle здесь для примеров кода "до / после": http://jsfiddle.net/Benjol/DjrRD/

Другие вопросы по тегам