jQuery adiado e promessas - .then () vs .done ()

473

Eu tenho lido sobre adiados e promessas do jQuery e não vejo a diferença entre usar .then()& .done()para retornos de chamada bem-sucedidos. Eu sei que Eric Hynds menciona isso .done()e .success()mapeia para a mesma funcionalidade, mas acho que o faz, .then()pois todos os retornos de chamada são invocados na conclusão de uma operação bem-sucedida.

Alguém pode me esclarecer sobre o uso correto?

screenm0nkey
fonte
15
Observe a todos que o JQuery 3.0 lançado em junho de 2016 foi a primeira versão compatível com as especificações Promises / A + e ES2015 Promises. A implementação anterior tinha incompatibilidades com o que as promessas deveriam cumprir.
Flimm
Atualizei minha resposta com uma recomendação aprimorada sobre o que usar quando.
Robert Siemer

Respostas:

577

Os retornos de chamada anexados done()serão acionados quando o adiado for resolvido. Os retornos de chamada anexados fail()serão acionados quando o adiado for rejeitado.

Antes do jQuery 1.8, then()era apenas açúcar sintático:

promise.then( doneCallback, failCallback )
// was equivalent to
promise.done( doneCallback ).fail( failCallback )

A partir do 1.8, then()é um alias para pipe()e retorna uma nova promessa, veja aqui para obter mais informações sobre pipe().

success()e error()estão disponíveis apenas no jqXHRobjeto retornado por uma chamada para ajax(). Eles são aliases simples para done()e fail()respectivamente:

jqXHR.done === jqXHR.success
jqXHR.fail === jqXHR.error

Além disso, done()não se limita a um único retorno de chamada e filtrará as não funções (embora exista um bug com as strings na versão 1.8 que deve ser corrigido na 1.8.1):

// this will add fn1 to 7 to the deferred's internal callback list
// (true, 56 and "omg" will be ignored)
promise.done( fn1, fn2, true, [ fn3, [ fn4, 56, fn5 ], "omg", fn6 ], fn7 );

O mesmo vale para fail().

Julian Aubourg
fonte
8
thendevolver uma nova promessa era uma coisa essencial que me faltava. Eu não conseguia entender por que uma corrente como $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })estava falhando com data2indefinida; quando mudei donepara thenele funcionou, porque eu estava realmente querendo prometer juntos, em vez de anexar mais manipuladores à promessa original.
wrschneider
5
O jQuery 3.0 é a primeira versão compatível com as especificações Promises / A + e ES2015.
Flimm
4
Eu ainda não entendo por que eu usaria um sobre o outro. Se eu fizer uma chamada ajax e precisar aguardar até que a chamada seja totalmente concluída (o que significa que a resposta é retornada do servidor) antes de fazer outra chamada ajax, eu uso doneou then? Por quê?
CodingYoshi
@CodingYoshi Confira minha resposta para finalmente responder a essa pergunta (uso .then()).
Robert Siemer
413

Também há diferença na maneira como os resultados de retorno são processados ​​(é chamado encadeamento, donenão thenencadeia enquanto produz cadeias de chamada)

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).then(function (x){
    console.log(x);
}).then(function (x){
    console.log(x)
})

Os seguintes resultados serão registrados:

abc
123
undefined

Enquanto

promise.done(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return 123;
}).done(function (x){
    console.log(x);
}).done(function (x){
    console.log(x)
})

obterá o seguinte:

abc
abc
abc

---------- Atualização:

Btw. Esqueci de mencionar, se você retornar um valor de Promessa em vez de tipo atômico, a promessa externa aguardará até que a promessa interna seja resolvida:

promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);
    return $http.get('/some/data').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });
}).then(function (result){
    console.log(result); // result === xyz
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

dessa maneira, torna-se muito simples compor operações assíncronas paralelas ou sequenciais, como:

// Parallel http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    var promise1 = $http.get('/some/data?value=xyz').then(function (result) {
        console.log(result); // suppose result === "xyz"
        return result;
    });

    var promise2 = $http.get('/some/data?value=uvm').then(function (result) {
        console.log(result); // suppose result === "uvm"
        return result;
    });

    return promise1.then(function (result1) {
        return promise2.then(function (result2) {
           return { result1: result1, result2: result2; }
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})

O código acima emite duas solicitações http em paralelo, tornando as solicitações concluídas mais rapidamente, enquanto abaixo dessas solicitações http estão sendo executadas sequencialmente, reduzindo a carga do servidor

// Sequential http requests
promise.then(function (x) { // Suppose promise returns "abc"
    console.log(x);

    return $http.get('/some/data?value=xyz').then(function (result1) {
        console.log(result1); // suppose result1 === "xyz"
        return $http.get('/some/data?value=uvm').then(function (result2) {
            console.log(result2); // suppose result2 === "uvm"
            return { result1: result1, result2: result2; };
        });
    });
}).then(function (result){
    console.log(result); // result === { result1: 'xyz', result2: 'uvm' }
}).then(function (und){
    console.log(und) // und === undefined, because of absence of return statement in above then
})
Lu4
fonte
121
+1 para a noção que donenão faz nada no resultado em que thenaltera o resultado. Enorme ponto perdido pelos outros imo.
Shanimal 14/05
9
Provavelmente vale a pena mencionar a qual versão do jQuery isso se aplica, já que o comportamento de then mudou em 1,8
bradley.ayers
4
+1 Direto ao ponto. Eu criei um exemplo executável se alguém quiser ver o que as cadeias com resultados mistos donee thenchama.
Michael Kropat
7
o exemplo acima também destaca que 'done' funciona no objeto de promessa original criado inicialmente, mas 'then' retorna uma nova promessa.
Pulak Kanti Bhattacharyya
2
Isso se aplica ao jQuery 1.8+. As versões mais antigas agem exatamente como no doneexemplo. Altere thenpara pipeno pré-1.8 para obter o thencomportamento 1.8+ .
David Harkness
57

.done() tem apenas um retorno de chamada e é o retorno de sucesso

.then() tem retornos de chamada com êxito e com falha

.fail() possui apenas um retorno de chamada com falha

então depende de você o que você deve fazer ... você se importa se é bem-sucedido ou se falha?

Marino Šimić
fonte
18
Você não mencionou que 'então' produz cadeias de chamadas. Veja a resposta do Lu4.
Oligofren #
Sua resposta é de 2011 ... Atualmente, seus valores de retorno são then()muito diferentes done(). Como then()geralmente é chamado apenas com o retorno de chamada bem-sucedido, seu ponto é mais um detalhe do que a principal coisa a lembrar / conhecer. (Não posso dizer como era antes do jQuery 3.0.)
Robert Siemer
14

deferred.done ()

adiciona manipuladores a serem chamados somente quando Adiado for resolvido . Você pode adicionar vários retornos de chamada para serem chamados.

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).done(doneCallback);

function doneCallback(result) {
    console.log('Result 1 ' + result);
}

Você também pode escrever acima assim,

function ajaxCall() {
    var url = 'http://jsonplaceholder.typicode.com/posts/1';
    return $.ajax(url);
}

$.when(ajaxCall()).then(doneCallback, failCallback);

deferred.then ()

adiciona manipuladores a serem chamados quando adiado for resolvido, rejeitado ou ainda estiver em andamento .

var url = 'http://jsonplaceholder.typicode.com/posts/1';
$.ajax(url).then(doneCallback, failCallback);

function doneCallback(result) {
    console.log('Result ' + result);
}

function failCallback(result) {
    console.log('Result ' + result);
}
Nipuna
fonte
sua postagem não deixa claro como thense comporta se nenhum failretorno de chamada for fornecido - ou seja, não captar o failcaso de maneira alguma
BM
O caso de falha gera uma exceção que pode ser capturada pelo nível superior do programa. Você também pode ver a exceção no console do JavaScript.
David Spector
10

Na verdade, há uma diferença bastante crítica, na medida em que os diferidos do jQuery devem ser implementações do Promises (e o jQuery3.0 realmente tenta trazê-los para as especificações).

A principal diferença entre concluído / então é que

  • .done() SEMPRE retorna os mesmos valores Promise / Wrapped com os quais começou, independentemente do que você faz ou do que você retorna.
  • .then() sempre retorna uma NOVA Promessa e você é responsável por controlar o que essa promessa é baseada no que a função que você passou nela retornou.

Traduzido do jQuery para o ES2015 Promises nativo, .done()é como implementar uma estrutura de "toque" em torno de uma função em uma cadeia Promise, na medida em que, se a cadeia estiver no estado "resolver", passará um valor para uma função .. mas o resultado dessa função NÃO afetará a própria cadeia.

const doneWrap = fn => x => { fn(x); return x };

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(doneWrap(console.log.bind(console)));

$.Deferred().resolve(5)
            .done(x => x + 1)
            .done(console.log.bind(console));

Ambos registrarão 5, não 6.

Observe que eu usei o done e doneWrap para fazer o log, não. Isso porque as funções console.log não retornam nada. E o que acontece se você passar uma função que não retorna nada?

Promise.resolve(5)
       .then(doneWrap( x => x + 1))
       .then(console.log.bind(console))
       .then(console.log.bind(console));

Isso registrará:

5

Indefinido

O que aconteceu? Quando eu usei .then e passei a ela uma função que não retornou nada, seu resultado implícito foi "indefinido" ... o que, é claro, retornou uma promessa [indefinida] para o método seguinte, que foi registrado indefinido. Portanto, o valor original com o qual começamos foi basicamente perdido.

.then()é, no fundo, uma forma de composição da função: o resultado de cada etapa é usado como argumento para a função na próxima etapa. É por isso que .done é melhor pensado como um "toque" -> na verdade não faz parte da composição, apenas algo que espreita o valor em uma determinada etapa e executa uma função nesse valor, mas na verdade não altera a composição de qualquer maneira.

Essa é uma diferença bastante fundamental, e provavelmente existe uma boa razão pela qual o Promises nativo não possui um método .done implementado. Não temos que explicar por que não existe um método .fail, porque isso é ainda mais complicado (a saber, .fail / .catch NÃO são espelhos das funções .done / .then -> do .catch que retornam valores nus. "stay" rejeitado como aqueles que foram passados ​​para .then, eles resolvem!)

Dtipson
fonte
6

then()sempre significa que será chamado em qualquer caso. Mas a passagem de parâmetros é diferente em diferentes versões do jQuery.

Antes do jQuery 1.8, then()é igual a done().fail(). E todas as funções de retorno de chamada compartilham os mesmos parâmetros.

Porém, a partir do jQuery 1.8, then()retorna uma nova promessa e, se retornar um valor, será passado para a próxima função de retorno de chamada.

Vamos ver o seguinte exemplo:

var defer = jQuery.Deferred();

defer.done(function(a, b){
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
}).then(function( a, b ) {
            return a + b;
}).done(function( result ) {
            console.log("result = " + result);
});

defer.resolve( 3, 4 );

Antes do jQuery 1.8, a resposta deveria ser

result = 3
result = 3
result = 3

Tudo resultleva 3. E a then()função sempre passa o mesmo objeto adiado para a próxima função.

Mas a partir do jQuery 1.8, o resultado deve ser:

result = 3
result = 7
result = NaN

Como a primeira then()função retorna uma nova promessa, e o valor 7 (e este é o único parâmetro que será transmitido) é passado para a próxima done(), então a segunda done()gravação result = 7. O segundo then()recebe 7 como valor de ae assume undefinedcomo valor de b, portanto, o segundo then()retorna uma nova promessa com o parâmetro NaN e o último done()imprime NaN como resultado.

JasmineOT
fonte
"then () sempre significa que será chamado em qualquer caso" - não é verdade. then () nunca é chamado em caso de erro dentro da Promessa.
David Spector
Aspecto interessante que um jQuery.Deferred()pode receber vários valores, que corretamente passa para o primeiro .then().-A pouco estranho embora ... como qualquer seguinte .then()não pode fazê-lo. (A interface escolhida via returnpode retornar apenas um valor.) O nativo do Javascript Promisenão faz isso. (O que é mais consistente, para ser honesto.)
Robert Siemer
3

Existe um mapeamento mental muito simples em resposta que foi um pouco difícil de encontrar nas outras respostas:

BM
fonte
2

Use apenas .then()

Essas são as desvantagens de .done()

  • não pode ser acorrentado
  • bloquear resolve()chamada (todos os .done()manipuladores serão executados de forma síncrona)
  • resolve()pode receber uma exceção de .done()manipuladores registrados (!)
  • uma exceção na .done()metade mata o diferido:
    • .done()manipuladores adicionais serão ignorados silenciosamente

Pensei temporariamente que .then(oneArgOnly)sempre exige .catch()que nenhuma exceção seja silenciosamente ignorada, mas isso não é mais verdade: o unhandledrejectionevento registra .then()exceções sem tratamento no console (como padrão). Muito razoável! Não há mais motivo para usar .done().

Prova

O seguinte trecho de código revela que:

  • todos os .done()manipuladores serão chamados síncronos no ponto deresolve()
    • registrado como 1, 3, 5, 7
    • registrado antes que o script caia na parte inferior
  • exceção em um chamador de .done()influênciasresolve()
    • registrado via catch around resolve()
  • quebras de exceção prometem .done()resolução adicional
    • 8 e 10 não estão registrados!
  • .then() não tem nenhum desses problemas
    • registrado como 2, 4, 6, 9, 11 após o thread ficar ocioso
    • (ambiente de trecho não tem unhandledrejectioné parece)

Aliás, as exceções de .done()não podem ser capturadas corretamente: por causa do padrão síncrono de .done(), o erro é lançado no ponto de .resolve()(pode ser o código da biblioteca!) Ou na .done()chamada que anexa o culpado se o adiado já tiver sido resolvido.

console.log('Start of script.');
let deferred = $.Deferred();
// deferred.resolve('Redemption.');
deferred.fail(() => console.log('fail()'));
deferred.catch(()=> console.log('catch()'));
deferred.done(() => console.log('1-done()'));
deferred.then(() => console.log('2-then()'));
deferred.done(() => console.log('3-done()'));
deferred.then(() =>{console.log('4-then()-throw');
    throw 'thrown from 4-then()';});
deferred.done(() => console.log('5-done()'));
deferred.then(() => console.log('6-then()'));
deferred.done(() =>{console.log('7-done()-throw');
    throw 'thrown from 7-done()';});
deferred.done(() => console.log('8-done()'));
deferred.then(() => console.log('9-then()'));

console.log('Resolving.');
try {
    deferred.resolve('Solution.');
} catch(e) {
    console.log(`Caught exception from handler
        in resolve():`, e);
}
deferred.done(() => console.log('10-done()'));
deferred.then(() => console.log('11-then()'));
console.log('End of script.');
<script
src="https://code.jquery.com/jquery-3.4.1.min.js"
integrity="sha384-vk5WoKIaW/vJyUAd9n/wmopsmNhiy+L2Z+SBxGYnUkunIxVxAv/UtMOhba/xskxh"
crossorigin="anonymous"
></script>

Robert Siemer
fonte
Algumas coisas: 1) Entendo o que você está dizendo que donenão será executado se um feito anterior tiver uma exceção. Mas por que isso seria ignorado silenciosamente, quero dizer que ocorreu uma exceção, por que você diz que é silencioso? 2) Eu desprezo o Deferredobjeto porque sua API está muito mal executada. É muito complexo e confuso. Seu código aqui também não ajuda a provar seu argumento e possui muita complexidade desnecessária para o que você está tentando provar. 3) Por que os doneíndices 2, 4 e 6 são executados antes do 2º then?
CodingYoshi
Meu mal, você definitivamente merece um voto. Quanto ao seu comentário sobre a exceção, normalmente é assim que as exceções funcionam: uma vez geradas, o código depois que não será executado. Além disso, a documentação do jQuery indica que ela só será executada se o adiado for resolvido.
CodingYoshi
@CodingYoshi A situação é diferente aqui: eu estava falando apenas de promessas / adiamentos resolvidos. Não estou reclamando que o resto do manipulador de sucesso não é chamado, isso é normal. Mas não vejo razão para que um manipulador de sucesso completamente diferente em uma promessa de sucesso não seja chamado. Todos .then()serão chamados, exceção (nesses manipuladores) aumentada ou não. Mas .done()quebra de adição / restante .
Robert Siemer 13/04
@CodingYoshi Melhorei bastante minha resposta, se posso dizer. Código e texto.
Robert Siemer
1

Há mais uma diferença vital no jQuery 3.0 que pode facilmente levar a comportamentos inesperados e não é mencionado nas respostas anteriores:

Considere o seguinte código:

let d = $.Deferred();
d.done(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

isto produzirá:

then
now

Agora substitua done() por then()no mesmo snippet:

var d = $.Deferred();
d.then(() => console.log('then'));
d.resolve();
console.log('now');
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.0.0/jquery.min.js"></script>

a saída é agora:

now
then

Portanto, para adiados resolvidos imediatamente, a função transmitida para done()sempre será invocada de maneira síncrona, enquanto qualquer argumento transmitido then()é invocado de forma assíncrona.

Isso difere das versões anteriores do jQuery, nas quais os dois retornos de chamada são chamados de forma síncrona, conforme mencionado no guia de atualização :

Outra mudança de comportamento necessária para a conformidade do Promises / A + é que retornos de chamada adiados .then () são sempre chamados de forma assíncrona. Anteriormente, se um retorno de chamada .then () fosse adicionado a um adiado que já estivesse resolvido ou rejeitado, o retorno de chamada seria executado imediatamente e de forma síncrona.

schellmax
fonte
-5

.done()encerra a cadeia de promessas, garantindo que nada mais possa anexar outras etapas. Isso significa que a implementação da promessa do jQuery pode gerar qualquer exceção não tratada, já que ninguém pode lidar com isso usando .fail().

Em termos práticos, se você não planeja anexar mais etapas a uma promessa, deve usá-lo .done(). Para mais detalhes, veja por que as promessas precisam ser feitas.

gleb bahmutov
fonte
6
Cuidado! Esta resposta seria correta para várias implementações promissoras, mas não para o jQuery, nas quais .done()não tem uma função final. A documentação diz: "Como deferred.done () retorna o objeto adiado, outros métodos do objeto adiado podem ser encadeados a esse, incluindo métodos adicionais .done ()". .fail()não é mencionado, mas sim, isso também pode ser encadeado.
Roamer-1888
1
My bad, não verificar o jQuery
gleb bahmutov
1
@glebbahmutov - talvez você deva excluir esta resposta para que outras pessoas não fiquem confusas? Apenas uma sugestão amigável :)
Andrey
2
Por favor, não apague a resposta, isso também pode ajudar as pessoas a esclarecer seus mal-entendidos.
Melissa