Когда мне следует использовать метод jQuery deferred «then», а когда - метод «конвейера»?

97

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

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

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

pipe()

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

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

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

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

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


Дополнение

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

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

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

Hippietrail
источник
1
Я думал об этом некоторое время, но, черт возьми, я не могу придумать ни одного варианта использования. Создание новых объектов обещаний может быть просто накладными расходами, если они вам не нужны (я не знаю, как они связаны друг с другом внутри). Тем не менее, безусловно, есть люди, которые понимают это лучше, чем я.
Felix Kling,
6
Любой, кто интересуется этим вопросом, наверняка заинтересуется билетом № 11010 в системе отслеживания ошибок jQuery: MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A
hippietrail

Ответы:

103

Поскольку jQuery 1.8 .then ведет себя так же, как .pipe:

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

и

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

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


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

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

  • Вы бы как-то использовали .pipe()(предварительную) фильтрацию результата. Возвращаемое значение обратного вызова для .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, не раскрывая фактический поток / порядок вызовов.


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

Феликс Клинг
источник
Ах да, я упустил из виду, что pipeможет делать фильтрацию, чего thenне может. Но при поиске в Google эти темы, кажется, они выбрали называть это, pipeа не filterпотому, что считали фильтрацию чем-то вроде дополнительного бонуса, который идет вместе с ней, тогда как pipeболее четко указывали ее истинную цель. Похоже, помимо фильтрации должны быть и другие отличия. (С другой стороны, я признаю, что не совсем понимаю функцию фильтрации даже с вашими примерами. Должно result values;быть return values;, кстати?)
hippietrail
Когда я говорю, что не понимаю ваших примеров, это что-то вроде этого: в верхнем примере оба .then()получают те же данные, resultкоторые вы фильтруете каждый раз; тогда как в нижнем примере, перед передачей .pipe()удаляет часть данных result, поскольку resultдва последующих .then()s получат?
hippietrail
1
@hippietrail: Тем временем я обновил свой ответ, а также включил другие цели .pipe(). Если обратный вызов возвращает отложенный объект, для этого объекта будут зарегистрированы последующие обратные вызовы выполненного или неудачного выполнения. Приведу еще один пример. изменить: относительно вашего второго комментария: да.
Феликс Клинг,
Итак, одно различие заключается в том, что данные проходят через, pipe() тогда как then()это больше похоже на листовой узел, в конце которого вы должны использовать свои данные, и он не течет дальше, и что, несмотря на тот факт, что он then()возвращает, Deferredон фактически не используется / не полезен? Если это правильно, может помочь уточнить включение чего-то вроде /* do something with "min"/"max" */в каждое предложение ".then ()".
hippietrail
1
Не беспокойтесь :) Мне также потребовалось некоторое время, чтобы полностью понять, как работают отложенные объекты и их методы. Но как только вы это поймете, это уже не будет трудным. Я согласен с тем, что документацию, вероятно, можно было бы написать проще.
Феликс Клинг,
7

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

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

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

Так что используйте, then()когда нужно, а pipe()когда нужно ...

Алекс Фейнман
источник
3
Я нашел реальный пример использования этих двух в блоге К. Скотта Аллена «Experiments In Writing»: Geolocation, Geocoding и jQuery Promises : «Тогда логика управления читается довольно хорошо:» $(function () { $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); }); «Обратите внимание, что pipe отличается от потом, потому что pipe дает новое обещание ".
hippietrail 02
5

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

Из комментарияjaubourg в тикете системы отслеживания ошибок jQuery № 11010 «MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A»:

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

(мина emphassis)

Hippietrail
источник
1
Лучшая ссылка на данный момент, я ищу продвинутые использования.
TWiStErRob