w3schools promises promesas javascript node.js dojo promise deferred

javascript - promises - Promesas encadenadas que no transmiten el rechazo



promesas javascript (3)

Tengo un problema para entender por qué los rechazos no se transmiten a través de una cadena de promesas y espero que alguien me pueda ayudar a entender por qué. Para mí, vincular la funcionalidad a una cadena de promesas implica la intención de cumplir con una promesa original. Es difícil de explicar, así que déjame mostrar un ejemplo de código de mi problema primero. (Nota: este ejemplo usa Nodo y el módulo de nodo diferido. Probé esto con Dojo 1.8.3 y tuve los mismos resultados)

var d = require("deferred"); var d1 = d(); var promise1 = d1.promise.then( function(wins) { console.log(''promise1 resolved''); return wins;}, function(err) { console.log(''promise1 rejected''); return err;}); var promise2 = promise1.then( function(wins) { console.log(''promise2 resolved''); return wins;}, function(err) { console.log(''promise2 rejected''); return err;}); var promise3 = promise2.then( function(wins) { console.log(''promise3 resolved''); return wins;}, function(err) { console.log(''promise3 rejected''); return err;}); d1.reject(new Error());

El resultado de ejecutar esta operación es este resultado:

promise1 rejected promise2 resolved promise3 resolved

De acuerdo, para mí, este resultado no tiene sentido. Al unirse a esta cadena de promesas, cada una de ellas implica la intención de que dependerá de la resolución exitosa de d1 y de que se transmita un resultado a lo largo de la cadena. Si la promesa en promise1 no recibe el valor de wins, sino que obtiene un valor err en su manejador de errores, ¿cómo es posible que se llame a la siguiente promesa de la cadena? No hay forma de que pueda transmitir un valor significativo a la siguiente promesa porque no obtuvo un valor en sí mismo.

Una forma diferente en que puedo describir lo que estoy pensando es: hay tres personas, John, Ginger y Bob. John es dueño de una tienda de widgets. Ginger ingresa a su tienda y solicita una bolsa de widgets de colores variados. Él no los tiene en stock, por lo que envía una solicitud a su distribuidor para que se los envíen. Mientras tanto, le da a Ginger un control de lluvia indicando que le debe la bolsa de artilugios. Bob descubre que Ginger está recibiendo los widgets y le pide que obtenga el widget azul cuando termine con ellos. Ella acepta y le da una nota indicando que lo hará. Ahora, el distribuidor de John no puede encontrar ningún accesorio en su suministro y el fabricante no los vuelve a fabricar, por lo que le informan a John, quien a su vez le informa a Ginger que no puede obtener los widgets. ¿Cómo puede Bob conseguir un widget azul de Ginger cuando no lo consiguió?

Una tercera perspectiva más realista que tengo sobre este tema es esta. Digamos que tengo dos valores que quiero actualizar a una base de datos. Uno depende del ID del otro, pero no puedo obtener el ID hasta que ya lo haya insertado en una base de datos y haya obtenido el resultado. Además de eso, la primera inserción depende de una consulta de la base de datos. La base de datos llama a las promesas de retorno que utilizo para encadenar las dos llamadas en una secuencia.

var promise = db.query({parent_id: value}); promise.then(function(query_result) { var first_value = { parent_id: query_result[0].parent_id } var promise = db.put(first_value); promise.then(function(first_value_result) { var second_value = { reference_to_first_value_id: first_value_result.id } var promise = db.put(second_value); promise.then(function(second_value_result) { values_successfully_entered(); }, function(err) { return err }); }, function(err) { return err }); }, function(err) { return err });

Ahora, en esta situación, si db.query fallara, llamaría a la función err del primero. Pero luego llamaría a la función de éxito de la próxima promesa. Si bien esa promesa está esperando los resultados del primer valor, en su lugar obtendría el mensaje de error de su función de manejo de errores.

Entonces, mi pregunta es, ¿por qué tendría una función de entrega de error si tengo que probar los errores en mi función de éxito?

Perdón por la duración de esto. Simplemente no sabía cómo explicarlo de otra manera.

ACTUALIZACIÓN y corrección

(Nota: eliminé una respuesta que había hecho una vez a algunos comentarios. Por lo tanto, si alguien comentó mi respuesta, sus comentarios pueden parecer fuera de contexto ahora que la eliminé. Lo siento, intento mantener esto lo más corto posible .)

Gracias a todos los que respondieron. Quisiera primero pedir disculpas a todos por escribir mi pregunta tan mal, especialmente mi pseudo código. Fui demasiado agresivo para tratar de mantenerlo corto.

Gracias a la respuesta de Bergi, creo que encontré el error en mi lógica. Creo que podría haber pasado por alto otro problema que estaba causando el problema que estaba teniendo. Esto posiblemente está causando que la cadena de la promesa funcione de forma diferente a lo que pensé que debería ser. Todavía estoy probando diferentes elementos de mi código, por lo que ni siquiera puedo formar una pregunta adecuada para ver qué estoy haciendo mal todavía. Sí, quería actualizarlos a todos y gracias por su ayuda.


Para mí, este resultado no tiene sentido. Al adherirnos a esta cadena de promesas, cada una implica la intención de que dependerá de la resolución exitosa de d1 y un resultado que se transmite por la cadena

No. Lo que está describiendo no es una cadena, sino que simplemente une todas las devoluciones de llamada a d1 . Sin embargo, si desea encadenar algo con then , el resultado de la promise2 depende de la resolución de la promise1 y de cómo lo manejaron las devoluciones de llamada .

El estado de los documentos:

Devuelve una nueva promesa para el resultado de la devolución de llamada (s).

El método .then generalmente se considera en términos de la especificación Promises / A (o el Promsises / A + aún más estricto). Eso significa que las devoluciones de shell invocan promesas que se asimilarán para convertirse en la resolución de promise2 , y si no hay controlador de éxito / error, el resultado respectivo pasará directamente a promise2 , por lo que simplemente puede omitir el controlador para propagar el error. .

Sin embargo, si se maneja el error, la promise2 resultante2 se considera fija y se cumplirá con ese valor. Si no quieres eso, deberías repetir el error , al igual que en una cláusula try-catch. Alternativamente, puede devolver una promesa rechazada (por serlo) del controlador. No estoy seguro de qué es la forma de rechazo de Dojo, sino:

var d1 = d(); var promise1 = d1.promise.then( function(wins) { console.log(''promise1 resolved''); return wins;}, function(err) { console.log(''promise1 rejected''); throw err;}); var promise2 = promise1.then( function(wins) { console.log(''promise2 resolved''); return wins;}, function(err) { console.log(''promise2 rejected''); throw err;}); var promise3 = promise2.then( function(wins) { console.log(''promise3 resolved''); return wins;}, function(err) { console.log(''promise3 rejected''); throw err;}); d1.reject(new Error());

¿Cómo puede Bob conseguir un widget azul de Ginger cuando no lo consiguió?

Él no debería poder. Si no hay manejadores de errores, él solo percibirá el mensaje (de (desde el distribuidor) de John) de Ginger que no quedan artilugios. Sin embargo, si Ginger configura un manejador de errores para ese caso, aún podría cumplir su promesa de darle a Bob un artilugio dándole un verde de su propia choza si no quedan los azules en John o su distribuidor.

Para traducir sus devoluciones de errores en el metapher, return err del manejador sería como decir "si no quedan widgets, solo dele la nota de que no quedan más, es tan bueno como el widget deseado".

En la situación de la base de datos, si db.query fallara, llamaría a la función err del primero entonces

... lo que significaría que el error se maneja allí. Si no lo haces, simplemente omite la devolución de llamada por error. Por cierto, sus callbacks de éxito no return las promesas que están creando, por lo que parecen ser bastante inútiles. Correcto sería:

var promise = db.query({parent_id: value}); promise.then(function(query_result) { var first_value = { parent_id: query_result[0].parent_id } var promise = db.put(first_value); return promise.then(function(first_value_result) { var second_value = { reference_to_first_value_id: first_value_result.id } var promise = db.put(second_value); return promise.then(function(second_value_result) { return values_successfully_entered(); }); }); });

o, dado que no necesita los cierres para acceder a los valores de resultados de las devoluciones de llamada anteriores, incluso:

db.query({parent_id: value}).then(function(query_result) { return db.put({ parent_id: query_result[0].parent_id }); }).then(function(first_value_result) { return db.put({ reference_to_first_value_id: first_value_result.id }); }.then(values_successfully_entered);


@Jordan, en primer lugar, como comentaron los comentaristas, al usar lib diferida, su primer ejemplo definitivamente produce el resultado que espera:

promise1 rejected promise2 rejected promise3 rejected

En segundo lugar, incluso si produjera resultados que sugiera, no afectaría el flujo de ejecución de su segundo fragmento, que es un poco diferente, más parecido a:

promise.then(function(first_value) { console.log(''promise1 resolved''); var promise = db.put(first_value); promise.then(function (second_value) { console.log(''promise2 resolved''); var promise = db.put(second_value); promise.then( function (wins) { console.log(''promise3 resolved''); }, function (err) { console.log(''promise3 rejected''); return err; }); }, function (err) { console.log(''promise2 rejected''); return err;}); }, function (err) { console.log(''promise1 rejected''); return err});

y que, en caso de que se rechace la primera promesa, solo saldrá:

promise1 rejected

Sin embargo ( llegando a la parte más interesante ) aunque la biblioteca diferida definitivamente devuelve 3 x rejected , la mayoría de las otras bibliotecas prometedoras devolverán 1 x rejected, 2 x resolved (eso lleva a la suposición de que obtuviste esos resultados usando alguna otra biblioteca de promesas) .

Lo que es adicionalmente confuso, esas otras bibliotecas son más correctas con su comportamiento. Dejame explicar.

En una contraparte mundial de sincronización de "promesa de rechazo" es throw . Entonces, semánticamente, async deferred.reject(new Error()) en sync es igual a throw new Error() . En su ejemplo, no está arrojando errores en sus devoluciones de llamada de sincronización, simplemente los devuelve, por lo tanto, cambia al flujo de éxito, con un error que es un valor de éxito. Para asegurarse de que el rechazo sea pasado más allá, necesita volver a lanzar sus errores:

function (err) { console.log(''promise1 rejected''); throw err; });

Así que ahora la pregunta es, ¿por qué la biblioteca diferida tomó el error devuelto como rechazo?

La razón de esto es que el rechazo en trabajos diferidos es un poco diferente. En Lib diferida, la regla es: promesa se rechaza cuando se resuelve con una instancia de error , por lo que incluso si se deferred.resolve(new Error()) actuará como deferred.reject(new Error()) , y si intenta hacer deferred.reject(notAnError) arrojará una excepción diciendo que esa promesa solo se puede rechazar con la instancia de error. Eso deja en claro por qué el error devuelto por la devolución de llamada rechaza la promesa.

Existe un razonamiento válido detrás de la lógica diferida, pero aún no está a la par de cómo funciona throw en JavaScript, y debido a que este comportamiento está programado para el cambio con la versión v0.7 de diferido.

Breve resumen:

Para evitar confusiones y resultados inesperados, solo siga las reglas de buenas prácticas:

  1. Siempre rechace sus promesas con instancias de error (siga las reglas del mundo de sincronización, donde arrojar un valor que no sea un error se considera una mala práctica).
  2. Rechazar las devoluciones de llamada de sincronización lanzando errores (devolverlos no garantiza el rechazo).

Obedeciendo a lo anterior, obtendrá resultados consistentes y esperados tanto en las bibliotecas promesas diferidas como en otras populares.


El uso puede envolver los errores en cada nivel de la Promesa. Encadené los errores en TraceError :

class TraceError extends Error { constructor(message, ...causes) { super(message); const stack = Object.getOwnPropertyDescriptor(this, ''stack''); Object.defineProperty(this, ''stack'', { get: () => { const stacktrace = stack.get.call(this); let causeStacktrace = ''''; for (const cause of causes) { if (cause.sourceStack) { // trigger lookup causeStacktrace += `/n${cause.sourceStack}`; } else if (cause instanceof Error) { causeStacktrace += `/n${cause.stack}`; } else { try { const json = JSON.stringify(cause, null, 2); causeStacktrace += `/n${json.split(''/n'').join(''/n '')}`; } catch (e) { causeStacktrace += `/n${cause}`; // ignore } } } causeStacktrace = causeStacktrace.split(''/n'').join(''/n ''); return stacktrace + causeStacktrace; } }); // access first error Object.defineProperty(this, ''cause'', {value: () => causes[0], enumerable: false, writable: false}); // untested; access cause stack with error.causes() Object.defineProperty(this, ''causes'', {value: () => causes, enumerable: false, writable: false}); } }

Uso

throw new TraceError(''Could not set status'', srcError, ...otherErrors);

Salida

Funciones

TraceError#cause - first error TraceError#causes - list of chained errors