jQuery difiere y promete - .then () vs .done ()

473

He estado leyendo sobre jQuery diferidos y promesas y no puedo ver la diferencia entre usar .then()& .done()para devoluciones de llamada exitosas. Sé que Eric Hynds menciona eso .done()y se .success()asigna a la misma funcionalidad, pero supongo que sí, .then()ya que todas las devoluciones de llamada se invocan al completar una operación exitosa.

¿Alguien puede iluminarme para el uso correcto?

screenm0nkey
fuente
15
Tenga en cuenta que JQuery 3.0 lanzado en junio de 2016 fue la primera versión que cumplió con las especificaciones Promises / A + y ES2015 Promises. La implementación anterior tenía incompatibilidades con lo que se suponía que cumplirían las promesas.
Flimm
Actualicé mi respuesta con una recomendación mejorada de qué usar cuando.
Robert Siemer

Respuestas:

577

Las devoluciones de llamada adjuntas se done()activarán cuando se resuelva el aplazado. Las devoluciones de llamada adjuntas se fail()activarán cuando se rechace el aplazado.

Antes de jQuery 1.8, then()era solo azúcar sintáctico:

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

A partir de 1.8, then()es un alias pipe()y devuelve una nueva promesa, consulte aquí para obtener más información pipe().

success()y error()solo están disponibles en el jqXHRobjeto devuelto por una llamada a ajax(). Son alias simples para done()y fail()respectivamente:

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

Además, done()no se limita a una única devolución de llamada y filtrará las no funciones (aunque hay un error con cadenas en la versión 1.8 que debería corregirse en 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 );

Lo mismo vale para fail().

Julian Aubourg
fuente
8
thendevolver una nueva promesa fue algo clave que me faltaba. No podía entender por qué una cadena como $.get(....).done(function(data1) { return $.get(...) }).done(function(data2) { ... })estaba fallando con data2indefinido; cuando cambié donea thenesto funcionó, porque realmente quería canalizar las promesas juntas en lugar de adjuntar más controladores a la promesa original.
wrschneider
55
jQuery 3.0 es la primera versión que cumple con las especificaciones Promises / A + y ES2015.
Flimm
44
Todavía no entiendo por qué usaría uno sobre el otro. Si hago una llamada ajax y necesito esperar hasta que esa llamada se haya completado por completo (lo que significa que la respuesta es devuelta desde el servidor) antes de llamar a otra llamada ajax, ¿uso doneo then? ¿Por qué?
CodificaciónYoshi
@CodingYoshi Mira mi respuesta para finalmente responder esa pregunta (uso .then()).
Robert Siemer
413

También hay una diferencia en la forma en que se procesan los resultados de retorno (se llama encadenamiento, doneno se encadena mientras thenproduce cadenas de llamadas)

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

Se registrarán los siguientes resultados:

abc
123
undefined

Mientras

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

obtendrá lo siguiente:

abc
abc
abc

---------- Actualización:

Por cierto. Olvidé mencionar que si devuelve una Promesa en lugar del valor de tipo atómico, la promesa externa esperará hasta que se resuelva la promesa interna:

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

de esta manera se vuelve muy sencillo componer operaciones asincrónicas paralelas o secuenciales 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
})

El código anterior emite dos solicitudes http en paralelo, lo que hace que las solicitudes se completen antes, mientras que debajo de esas solicitudes http se ejecutan secuencialmente, lo que reduce la carga del 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
fuente
121
+1 para la noción que doneno hace nada al resultado donde thencambia el resultado. Enorme punto perdido por los demás imo.
Shanimal
99
Probablemente valga la pena mencionar a qué versión de jQuery se aplica esto, ya que el comportamiento thencambió en 1.8
bradley.ayers
44
+1 Directo al grano. Creé un ejemplo ejecutable si alguien quiere ver en qué cadenas hay resultados mixtos doney de thenllamadas.
Michael Kropat
77
el ejemplo anterior también resalta que 'hecho' funciona en el objeto de promesa original creado inicialmente pero 'luego' devuelve una nueva promesa.
Pulak Kanti Bhattacharyya
2
Esto se aplica a jQuery 1.8+. Las versiones anteriores actúan como el doneejemplo. Cambie thena pipepre-1.8 para obtener el thencomportamiento 1.8+ .
David Harkness
57

.done() solo tiene una devolución de llamada y es la devolución de llamada exitosa

.then() tiene devoluciones de llamada exitosas y fallidas

.fail() solo tiene una devolución de llamada fallida

entonces depende de usted lo que debe hacer ... ¿le importa si tiene éxito o si falla?

Marino Šimić
fuente
18
No menciona que 'entonces' produce cadenas de llamadas. Ver la respuesta de Lu4.
oligofren
Su respuesta es de 2011 ... Hoy en día sus valores de retorno son then()muy diferentes de done(). Como a then()menudo se llama solo con la devolución de llamada exitosa, su punto es más un detalle que lo principal para recordar / conocer. (No puedo decir cómo era antes de jQuery 3.0.)
Robert Siemer
14

diferido.done ()

agrega manejadores a los que se llamará solo cuando se resuelva Diferido . Puede agregar múltiples devoluciones de llamada para ser llamado.

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

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

También puedes escribir arriba así,

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

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

deferred.then ()

agrega manejadores a los que se llama cuando se difiere, se rechaza o aún está en progreso .

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
fuente
su publicación no aclara cómo se thencomporta si no failse proporciona una devolución de llamada, es decir, no captura el failcaso en absoluto
BM
El caso de falla genera una excepción que puede ser detectada por el nivel superior del programa. También puede ver la excepción en la consola de JavaScript.
David Spector el
10

En realidad, hay una diferencia bastante crítica, en la medida en que los aplazamientos de jQuery están destinados a ser implementaciones de Promesas (y jQuery3.0 en realidad trata de ponerlas en especificación).

La diferencia clave entre hecho / entonces es que

  • .done() SIEMPRE devuelve los mismos valores Promise / wrap con los que comenzó, independientemente de lo que haga o lo que devuelva.
  • .then() siempre devuelve una NUEVA promesa, y usted está a cargo de controlar qué promesa se basa en la función que le devolvió.

Traducido de jQuery a Promesas ES2015 nativas, .done()es como implementar una estructura "tap" alrededor de una función en una cadena Promise, en el sentido de que, si la cadena está en el estado "resolver", pasará un valor a una función. . pero el resultado de esa función NO afectará la cadena en sí.

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án 5, no 6.

Tenga en cuenta que usé done y doneWrap para hacer el registro, no .then. Eso es porque las funciones de console.log en realidad no devuelven nada. ¿Y qué pasa si pasa .then una función que no devuelve nada?

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

Eso registrará:

5 5

indefinido

¿Que pasó? Cuando utilicé .then y le pasé una función que no devolvió nada, su resultado implícito fue "indefinido" ... lo que, por supuesto, devolvió una Promesa [indefinida] al siguiente método, que se registró indefinido. Entonces, el valor original con el que comenzamos se perdió básicamente.

.then()es, en el fondo, una forma de composición de funciones: el resultado de cada paso se usa como argumento para la función en el siguiente paso. Es por eso que .done se considera mejor como un "toque" -> en realidad no es parte de la composición, solo algo que muestra un poco el valor en un determinado paso y ejecuta una función en ese valor, pero en realidad no altera La composición de cualquier manera.

Esta es una diferencia bastante fundamental, y probablemente haya una buena razón por la cual las Promesas nativas no tienen implementado un método .done. No tenemos que entender por qué no hay un método .fail, porque eso es aún más complicado (a saber, .fail / .catch NO son espejos de las funciones .done / .then -> en .catch que devuelven valores simples no "permanecer" rechazado como los que se pasan a. ¡Entonces, se resuelven!)

Dtipson
fuente
6

then()siempre significa que se llamará en cualquier caso. Pero los parámetros que pasan son diferentes en diferentes versiones de jQuery.

Antes de jQuery 1.8, then()es igual done().fail(). Y todas las funciones de devolución de llamada comparten los mismos parámetros.

Pero a partir de jQuery 1.8, then()devuelve una nueva promesa, y si ha devuelto un valor, se pasará a la siguiente función de devolución de llamada.

Veamos el siguiente ejemplo:

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 de jQuery 1.8, la respuesta debería ser

result = 3
result = 3
result = 3

Todo resulttoma 3. Y la then()función siempre pasa el mismo objeto diferido a la siguiente función.

Pero a partir de jQuery 1.8, el resultado debería ser:

result = 3
result = 7
result = NaN

Debido a que la primera then()función devuelve una nueva promesa, y el valor 7 (y este es el único parámetro que se transmitirá) se pasa a la siguiente done(), por lo que la segunda done()escritura result = 7. El segundo then()toma 7 como valor de ay toma undefinedcomo valor de b, por lo que el segundo then()devuelve una nueva promesa con el parámetro NaN, y el último done()imprime NaN como resultado.

JasmineOT
fuente
"then () siempre significa que se llamará en cualquier caso", no es cierto. then () nunca se llama en caso de error dentro de Promise.
David Spector el
Aspecto interesante de que a jQuery.Deferred()puede recibir múltiples valores, que pasa correctamente al primero .then(). Sin embargo, es un poco extraño ... ya que cualquiera de los siguientes .then()no puede hacerlo. (La interfaz elegida a través de returnsolo puede devolver un valor). El nativo de Javascript Promiseno hace eso. (Lo cual es más consistente, para ser honesto)
Robert Siemer
3

Hay un mapeo mental muy simple en respuesta que fue un poco difícil de encontrar en las otras respuestas:

BM
fuente
2

Uso único .then()

Estas son las desventajas de .done()

  • no puede ser encadenado
  • bloquear la resolve()llamada (todos los .done()controladores se ejecutarán sincrónicamente)
  • resolve()podría obtener una excepción de los .done()controladores registrados (!)
  • una excepción en un .done()medio mata al diferido:
    • .done()se omitirán silenciosamente otros controladores

Pensé temporalmente que .then(oneArgOnly)siempre se requiere .catch()para que no se ignore ninguna excepción en silencio, pero eso ya no es cierto: el unhandledrejectionevento registra .then()excepciones no controladas en la consola (por defecto). ¡Muy razonable! No hay razón para usar .done()en absoluto.

Prueba

El siguiente fragmento de código revela que:

  • todos los .done()manejadores se llamarán sincrónicos en el punto deresolve()
    • registrado como 1, 3, 5, 7
    • registrado antes de que el script caiga por la parte inferior
  • excepción en una llamada de .done()influenciasresolve()
    • registrado a través de catch around resolve()
  • excepción rompe promesa de mayor .done()resolución
    • ¡8 y 10 no están registrados!
  • .then() no tiene ninguno de estos problemas
    • registrado como 2, 4, 6, 9, 11 después de que el hilo se vuelve inactivo
    • (El entorno de fragmento no tiene unhandledrejectiones parece)

Por cierto, las excepciones de .done()no se pueden capturar correctamente: debido al patrón sincrónico de .done(), el error se produce en el punto de .resolve()(¡podría ser el código de la biblioteca!) O en la .done()llamada que une al culpable si el diferido ya está resuelto.

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
fuente
Algunas cosas: 1) Veo lo que está diciendo que doneno se ejecutará si un hecho anterior tiene una excepción. Pero por qué sería ignorado en silencio, quiero decir que ocurrió una excepción, entonces ¿por qué dices que es silencioso? 2) Desprecio el Deferredobjeto porque su API está muy mal hecha. Es demasiado complejo y confuso. Su código aquí tampoco ayuda a probar su punto y tiene demasiada complejidad innecesaria para lo que está tratando de probar. 3) ¿Por qué los doneíndices 2, 4 y 6 se realizan antes del segundo then?
CodingYoshi
Lo malo, definitivamente mereces un voto. En cuanto a su comentario sobre la excepción, normalmente así es como funcionan las excepciones: una vez que se genera, el código después no se ejecutará. Además, la documentación de jQuery establece que solo se ejecutará si se resuelve el aplazado.
CodificaciónYoshi
@CodingYoshi La situación es diferente aquí: solo estaba hablando de promesas / aplazamientos resueltos. No me quejo de que el resto del controlador de éxito no se llame, eso es normal. Pero no veo ninguna razón por la cual no se llama a un controlador de éxito completamente diferente en una promesa exitosa. Todos .then()serán llamados, excepción (en esos controladores) planteados o no. Pero además / .done()descanso restante .
Robert Siemer
@CodingYoshi Mejoré enormemente mi respuesta, si se me permite decir. Código y texto.
Robert Siemer
1

Hay una diferencia vital más a partir de jQuery 3.0 que puede conducir fácilmente a un comportamiento inesperado y no se menciona en las respuestas anteriores:

Considere el siguiente 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>

esto dará como resultado:

then
now

Ahora, reemplace done()por then()en el mismo fragmento:

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>

la salida es ahora:

now
then

Por lo tanto, para los aplazamientos resueltos de inmediato, la función pasada a done()siempre se invocará de forma síncrona, mientras que cualquier argumento pasado a then()se invoca de forma asíncrona.

Esto difiere de las versiones anteriores de jQuery donde ambas devoluciones de llamada se llaman sincrónicamente, como se menciona en la guía de actualización :

Otro cambio de comportamiento requerido para el cumplimiento de Promesas / A + es que las devoluciones de llamada diferidas .then () siempre se llaman de forma asíncrona. Anteriormente, si se agregaba una devolución de llamada .then () a un diferido que ya se había resuelto o rechazado, la devolución de llamada se ejecutaría de forma inmediata y sincrónica.

schellmax
fuente
-5

.done()termina la cadena de promesa, asegurándose de que nada más pueda adjuntar más pasos. Esto significa que la implementación de la promesa jQuery puede arrojar cualquier excepción no controlada, ya que nadie puede manejarla usando .fail().

En términos prácticos, si no planea adjuntar más pasos a una promesa, debe usarlos .done(). Para más detalles, vea por qué las promesas deben hacerse

gleb bahmutov
fuente
66
¡Precaución! Esta respuesta sería correcta para varias implementaciones prometedoras, pero no jQuery, en la que .done()no tiene un rol de terminación. La documentación dice: "Dado que deferred.done () devuelve el objeto diferido, otros métodos del objeto diferido se pueden encadenar a este, incluidos los métodos adicionales .done ()". .fail()no se menciona, pero sí, eso también podría estar encadenado.
Roamer-1888
1
Mi mal, no
revisó
1
@glebbahmutov: ¿tal vez deberías eliminar esta respuesta para que otros no se confundan? Sólo una sugerencia amigable :)
Andrey
2
No elimine la respuesta, esto también puede ayudar a las personas a aclarar sus malentendidos.
Melissa