Quando devo usar o método “then” do jQuery deferred e quando devo usar o método “pipe”?

97

O jQuery's Deferredtem duas funções que podem ser usadas para implementar o encadeamento assíncrono de funções:

then()

deferred.then( doneCallbacks, failCallbacks ) Returns: Deferred

doneCallbacks Uma função, ou matriz de funções, chamada quando o Deferred é resolvido.
failCallbacks Uma função, ou matriz de funções, chamada quando o Deferred é rejeitado.

pipe()

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

doneFilter Uma função opcional que é chamada quando o Adiado é resolvido.
failFilter Uma função opcional que é chamada quando Deferred é rejeitado.

Eu sei que then()já existe há pouco mais tempo pipe(), o último deve adicionar algum benefício extra, mas qual é a diferença precisamente me escapa. Ambos usam praticamente os mesmos parâmetros de retorno de chamada, embora difiram no nome e a diferença entre retornar a Deferrede retornar a Promiseparece pequena.

Eu li os documentos oficiais várias vezes, mas sempre os achei muito "densos" para realmente entender e a pesquisa encontrou muitas discussões sobre um recurso ou outro, mas não encontrei nada que realmente esclareça os diferentes Prós e contras de cada.

Então, quando é melhor usar thene quando é melhor usar pipe?


Adição

A excelente resposta de Felix realmente ajudou a esclarecer como essas duas funções diferem. Mas eu me pergunto se há momentos em que a funcionalidade de then()é preferível à de pipe().

É evidente que pipe()é mais poderoso do que then()e parece que o primeiro pode fazer tudo o que o último pode fazer. Um motivo de uso then()pode ser que seu nome reflete sua função como o término de uma cadeia de funções que processam os mesmos dados.

Mas há um caso de uso que requer then()a devolução do original Deferredque não pode ser feito pipe()devido ao retorno de um novo Promise?

hippietrail
fonte
1
Eu pensei sobre isso por um tempo, mas tbh, não consigo pensar em nenhum caso de uso. Pode ser apenas uma sobrecarga criar novos objetos de promessa se você não precisar deles (não sei como eles são encadeados internamente). Dito isso, certamente há pessoas que entendem isso melhor do que eu.
Felix Kling
6
Qualquer pessoa interessada nesta questão certamente estará interessada no Ticket # 11010 no rastreador de bug do jQuery: MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A
hippietrail

Respostas:

103

Já que o jQuery 1.8 .then se comporta da mesma forma que .pipe:

Aviso de suspensão de uso: a partir do jQuery 1.8, o deferred.pipe()método está obsoleto. O deferred.then()método, que o substitui, deve ser usado em seu lugar.

e

A partir do jQuery 1.8 , o deferred.then()método retorna uma nova promessa que pode filtrar o status e os valores de um adiado por meio de uma função, substituindo o deferred.pipe()método agora obsoleto .

Os exemplos abaixo ainda podem ser úteis para alguns.


Eles têm finalidades diferentes:

  • .then()é para ser usado sempre que você quiser trabalhar com o resultado do processo, ou seja, como diz a documentação, quando o objeto diferido é resolvido ou rejeitado. É o mesmo que usar .done()ou .fail().

  • Você usaria .pipe()para (pré) filtrar o resultado de alguma forma. O valor de retorno de um retorno de chamada para .pipe()será passado como argumento para os retornos de chamada donee fail. Ele também pode retornar outro objeto adiado e os seguintes callbacks serão registrados neste adiado.

    Esse não é o caso com .then()(ou .done(), .fail()), os valores de retorno dos retornos de chamada registrados são simplesmente ignorados.

Portanto, não é que você use tanto .then() ou .pipe() . Você poderia usar .pipe()para os mesmos fins, .then()mas o inverso não é válido.


Exemplo 1

O resultado de alguma operação é uma matriz de objetos:

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

e você deseja calcular o mínimo e o máximo dos valores. Vamos supor que usamos dois donecallbacks:

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" */ 

});

Em ambos os casos, você deve iterar pela lista e extrair o valor de cada objeto.

Não seria melhor extrair os valores de alguma forma com antecedência para que você não tenha que fazer isso em ambos os retornos de chamada individualmente? Sim! E é isso que podemos usar .pipe()para:

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" */

});

Obviamente, este é um exemplo inventado e há muitas maneiras diferentes (talvez melhores) de resolver esse problema, mas espero que ilustre o ponto.


Exemplo 2

Considere as chamadas Ajax. Às vezes, você deseja iniciar uma chamada Ajax após a conclusão de uma anterior. Uma maneira é fazer a segunda chamada dentro de um doneretorno de chamada:

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

Agora, vamos supor que você deseja desacoplar seu código e colocar essas duas chamadas Ajax dentro de uma função:

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

Você gostaria de usar o objeto deferido para permitir que outro código que chama makeCallspara anexar retornos de chamada para a segunda chamada Ajax, mas

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

não teria o efeito desejado, pois a segunda chamada é feita dentro de um doneretorno de chamada e não pode ser acessada externamente.

A solução seria usar .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
});

Usando, .pipe()agora você pode tornar possível anexar retornos de chamada à chamada Ajax "interna" sem expor o fluxo / ordem real das chamadas.


Em geral, os objetos adiados fornecem uma maneira interessante de desacoplar seu código :)

Felix Kling
fonte
Ah, sim, esqueci que pipeposso fazer uma filtragem que thennão posso fazer. Mas, ao pesquisar esses tópicos no Google, parece que eles escolheram chamá-lo em pipevez de filterporque consideraram a filtragem como uma espécie de bônus extra que veio com ela, enquanto pipeindicava mais claramente seu verdadeiro propósito. Portanto, parece que deve haver outras diferenças além da filtragem. (Mais uma vez, admito que realmente não entendo o recurso de filtragem, mesmo com seus exemplos. Deve result values;ser return values;a propósito?)
hippietrail
Quando digo que não entendo seus exemplos, é algo assim: No exemplo superior, os dois .then()s recebem os mesmos dados resultque você filtra a cada vez; enquanto no exemplo inferior, o .pipe()remove alguns dos dados em seu resultantes de transmiti-los, pois resultos dois .then()s subsequentes receberão?
hippietrail
1
@hippietrail: atualizei minha resposta entretanto e incluí os outros propósitos de .pipe(). Se o retorno de chamada retornar um objeto adiado, retornos de chamada subsequentes concluídos ou com falha serão registrados para esse objeto. Vou incluir outro exemplo. editar: em relação ao seu segundo comentário: sim.
Felix Kling
Assim, uma diferença é que os dados fluem através pipe() enquanto then()é mais como um nó folha no final dos quais você deve usar seus dados e não flui mais adiante, e que, apesar do fato de que then()retorna a Deferredele não é realmente usado / útil? Se estiver correto, pode ser útil esclarecer incluir algo como /* do something with "min"/"max" */em cada "cláusula .then ()".
hippietrail
1
Não se preocupe :) Levei algum tempo também para compreender totalmente como os objetos adiados e seus métodos funcionam. Mas, uma vez que você entende isso, não parece mais ser difícil. Concordo que a documentação provavelmente poderia ser escrita de uma maneira mais fácil.
Felix Kling
7

Não há nenhum caso em que você DEVE usar then()over pipe(). Você sempre pode optar por ignorar o valor que pipe()será transmitido. Pode haver um leve impacto no desempenho para uso pipe- mas é improvável que importe.

Portanto, pode parecer que você simplesmente sempre pode usar pipe()em ambos os casos. No entanto , ao usar pipe(), você está comunicando a outras pessoas que estão lendo seu código (incluindo você, daqui a seis meses) que há alguma importância no valor de retorno. Se você o está descartando, está violando essa construção semântica.

É como ter uma função que retorna um valor que nunca é usado: confuso.

Portanto, use then()quando você deve, e pipe()quando você deve ...

Alex Feinman
fonte
3
Encontrei um exemplo da vida real usando os dois no blog de K. Scott Allen, "Experiments In Writing": Geolocation, Geocoding e jQuery Promises : "Então a lógica de controle lê muito bem:" $(function () { $.when(getPosition()) .pipe(lookupCountry) .then(displayResults); }); "Observe que o tubo é diferente de então, porque o cachimbo devolve uma nova promessa. "
hippietrail
5

Na verdade, descobriu-se que a diferença entre .then()e .pipe()foi considerada desnecessária e eles foram considerados iguais aos da versão 1.8 do jQuery.

De um comentário feitojaubourg no tíquete do rastreador de bug da jQuery # 11010 "MAKE DEFERRED.THEN == DEFERRED.PIPE LIKE PROMISE / A":

No 1.8, vamos remover o antigo e substituí-lo pelo tubo atual. Mas a conseqüência muito triste é que teremos que dizer às pessoas para usar o não-padrão feito, falha e progresso, porque a proposta não fornece meios simples, EFICIENTES, para apenas adicionar um retorno de chamada.

(ênfase é minha)

hippietrail
fonte
1
Melhor referência até agora, estou procurando usos avançados.
TWiStErRob