Когда следует использовать jQuery отложенный метод "then" и когда следует использовать метод "pipe"?

jQuery Deferred имеет две функции, которые могут использоваться для реализации асинхронной цепочки функций:

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

doneCallbacks Функция или массив функций, вызываемых при разрешении отложенного.
failCallbacks Функция или массив функций, вызываемых при отклонении Отложенного.

pipe()

deferred.pipe( [doneFilter] [, failFilter] ) Returns: Promise

doneFilter Необязательная функция, вызываемая при разрешении Отложенного.
failFilter Необязательная функция, вызываемая при отклонении Отложенного.

Я знаю, что then() было вокруг немного больше, чем pipe(), поэтому последнее должно добавить дополнительную выгоду, но какая разница именно ускользает от меня. Оба имеют почти одинаковые параметры обратного вызова, хотя они отличаются по названию, и разница между возвратом Deferred и возвратом Promise кажется незначительной.

Я читал официальные документы снова и снова, но всегда считаю их слишком "плотными", чтобы действительно обернуть мою голову, и поиск нашел много обсуждений одной или другой функции, но я не нашел ничего, что действительно разъясняет разные плюсы и минусы каждого.

Итак, когда лучше использовать then, и когда лучше использовать pipe?


Добавление

Отличный ответ Феликса действительно помог прояснить, как эти две функции отличаются. Но мне интересно, есть ли моменты, когда функциональность then() предпочтительнее функциональности pipe().

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

Но существует ли прецедент, для которого требуется then() вернуть исходный Deferred, который не может быть выполнен с помощью pipe() из-за того, что он возвращает новый Promise?

Ответ 1

Так как jQuery 1.8 .then ведет себя так же, как .pipe:

Уведомление об изнашивании: Как и в jQuery 1.8, метод deferred.pipe() устарел. Вместо этого следует использовать метод deferred.then(), который заменяет его.

и

С jQuery 1.8 метод deferred.then() возвращает новое обещание, которое может фильтровать состояние и значения отложенных через функцию, заменяя теперь устаревший метод deferred.pipe().

Нижеприведенные примеры могут быть полезны для некоторых.


Они служат для разных целей:

  • .then() должен использоваться всякий раз, когда вы хотите работать с результатом процесса, то есть, как говорится в документации, когда отложенный объект разрешен или отклонен. Это то же самое, что использовать .done() или .fail().

  • Вы использовали бы .pipe() для (pre) для фильтрации результата. Возвращаемое значение обратного вызова на .pipe() будет передано в качестве аргумента для обратных вызовов done и fail. Он также может вернуть другой объект с отсрочкой, и на этом отсрочке будут зарегистрированы следующие обратные вызовы.

    Это не относится к .then() (или .done(), .fail()), возвращаемые значения зарегистрированных обратных вызовов просто игнорируются.

Таким образом, вы не используете либо .then(), либо .pipe(). Вы можете использовать .pipe() для тех же целей, что и .then(), но обратное не выполняется.


Пример 1

Результатом некоторой операции является массив объектов:

[{value: 2}, {value: 4}, {value: 6}]

и вы хотите вычислить минимальные и максимальные значения. Предположим, мы используем два обратных вызова done:

deferred.then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var min = Math.min.apply(Math, values);

   /* do something with "min" */

}).then(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    var max = Math.max.apply(Math, values);

   /* do something with "max" */ 

});

В обоих случаях вам нужно перебирать список и извлекать значение из каждого объекта.

Не было бы лучше каким-то образом извлечь значения заранее, чтобы вам не приходилось делать это в обоих обратных вызовах индивидуально? Да! И это то, что мы можем использовать .pipe() для:

deferred.pipe(function(result) {
    // result = [{value: 2}, {value: 4}, {value: 6}]

    var values = [];
    for(var i = 0, len = result.length; i < len; i++) {
        values.push(result[i].value);
    }
    return values; // [2, 4, 6]

}).then(function(result) {
    // result = [2, 4, 6]

    var min = Math.min.apply(Math, result);

    /* do something with "min" */

}).then(function(result) {
    // result = [2, 4, 6]

    var max = Math.max.apply(Math, result);

    /* do something with "max" */

});

Очевидно, что это составленный пример, и есть много разных (возможно, лучших) способов решения этой проблемы, но я надеюсь, что это иллюстрирует суть.


Пример 2

Рассмотрим Ajax-вызовы. Иногда вы хотите инициировать один вызов Ajax после завершения предыдущего. Один из способов - сделать второй вызов внутри обратного вызова done:

$.ajax(...).done(function() {
    // executed after first Ajax
    $.ajax(...).done(function() {
        // executed after second call
    });
});

Теперь давайте предположим, что вы хотите развязать свой код и поместить эти два вызова Ajax внутри функции:

function makeCalls() {
    // here we return the return value of `$.ajax().done()`, which
    // is the same deferred object as returned by `$.ajax()` alone

    return $.ajax(...).done(function() {
        // executed after first call
        $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

Вы хотите использовать отложенный объект, чтобы разрешить другой код, который вызывает makeCalls для присоединения обратных вызовов для второго вызова Ajax, но

makeCalls().done(function() {
    // this is executed after the first Ajax call
});

не будет иметь желаемого эффекта, поскольку второй вызов выполняется внутри обратного вызова done и недоступен извне.

Вместо этого было бы использовать .pipe():

function makeCalls() {
    // here we return the return value of `$.ajax().pipe()`, which is
    // a new deferred/promise object and connected to the one returned
    // by the callback passed to `pipe`

    return $.ajax(...).pipe(function() {
        // executed after first call
        return $.ajax(...).done(function() {
            // executed after second call
        });
    });
}

makeCalls().done(function() {
    // this is executed after the second Ajax call
});

Используя .pipe(), вы можете теперь добавить обратные вызовы к "внутреннему" вызову Ajax, не подвергая действительный поток/порядок вызовов.


В общем, отложенные объекты предоставляют интересный способ отделить ваш код:)

Ответ 2

Нет случая, когда вы ДОЛЖНЫ использовать then() над pipe(). Вы всегда можете игнорировать значение, которое будет проходить pipe(). Может быть небольшое повышение производительности при использовании pipe - но это вряд ли имеет значение.

Так может показаться, что вы всегда можете использовать pipe() в обоих случаях. Однако, используя pipe(), вы общаетесь с другими людьми, читающими ваш код (включая себя, через полгода), что имеет значение для возвращаемого значения. Если вы отбрасываете его, вы нарушаете эту семантическую конструкцию.

Ему нравится иметь функцию, которая возвращает значение, которое никогда не используется: запутанное.

Поэтому используйте then(), когда вам нужно, и pipe(), когда вы должны...

Ответ 3

На самом деле получается, что разница между .then() и .pipe() считается ненужной, и они были сделаны такими же, как и для jQuery версии 1.8.

Из комментарий jaubourg в jQuery tuger билет # 11010 "СДЕЛАЙТЕ ДЕФРИРОВАН. THEN == DEFERRED.PIPE LIKE PROMISE/A":

В 1.8 мы удалим старое и заменим его на текущий канал. Но самое печальное следствие состоит в том, что мы должны будем сказать людям использовать нестандартные сделанные, неудачные и прогресс, потому что предложение не обеспечивает простой, ЭФФЕКТИВНЫЙ, означает просто добавить обратный вызов.

(emassis mine)