w3schools promises promesas encadenamiento ejemplos ejemplo await async asincronia javascript node.js promise bluebird

javascript - promises - Manejo de mĂșltiples capturas en la cadena de promesa



promise javascript (7)

Me pregunto si hay una manera de forzar de alguna manera a la cadena a detenerse en un cierto punto en función de los errores

No. No puedes realmente "terminar" una cadena, a menos que arrojes una excepción que burbujee hasta su final. Vea la respuesta de Benjamin Gruenbaum sobre cómo hacer eso.

Una derivación de su patrón sería no distinguir los tipos de error, sino utilizar errores que tengan campos statusCode y body que pueden enviarse desde un único controlador genérico .catch . Dependiendo de la estructura de su aplicación, su solución podría ser más limpia.

o si hay una mejor manera de estructurar esto para obtener algún tipo de comportamiento de ramificación

Sí, puedes hacer ramificaciones con promesas . Sin embargo, esto significa abandonar la cadena y "volver" a anidar, tal como lo haría en una instrucción anidada if-else o try-catch:

repository.Query(getAccountByIdQuery) .then(function(account) { return convertDocumentToModel(account) .then(verifyOldPassword) .then(function(verification) { return changePassword(verification) .then(function() { res.status(200).send(); }) }, function(verificationError) { res.status(406).send({ OldPassword: error }); }) }, function(accountError){ res.status(404).send({ error: "No account found with this Id" }); }) .catch(function(error){ console.log(error); res.status(500).send({ error: "Unable to change password" }); });

Todavía soy bastante nuevo en las promesas y estoy usando bluebird actualmente, sin embargo, tengo un escenario en el que no estoy muy seguro de cómo tratarlo mejor.

Entonces, por ejemplo, tengo una cadena de promesa dentro de una aplicación express como esta:

repository.Query(getAccountByIdQuery) .catch(function(error){ res.status(404).send({ error: "No account found with this Id" }); }) .then(convertDocumentToModel) .then(verifyOldPassword) .catch(function(error) { res.status(406).send({ OldPassword: error }); }) .then(changePassword) .then(function(){ res.status(200).send(); }) .catch(function(error){ console.log(error); res.status(500).send({ error: "Unable to change password" }); });

Entonces el comportamiento que busco es:

  • Va a obtener cuenta por ID
  • Si hay un rechazo en este punto, bombardea y devuelve un error
  • Si no hay error, convierta el documento devuelto a un modelo
  • Verifique la contraseña con el documento de la base de datos
  • Si las contraseñas no coinciden, bombardea y devuelve un error diferente
  • Si no hay error, cambie las contraseñas
  • Luego devuelve el éxito
  • Si algo más salió mal, devuelve 500

Por lo tanto, las capturas actuales no parecen detener el encadenamiento, y eso tiene sentido, por lo que me pregunto si hay una manera de forzar de alguna manera la cadena a detenerse en un cierto punto en función de los errores, o si hay una mejor manera estructurar esto para obtener algún tipo de comportamiento de ramificación, ya que hay un caso de if X do Y else Z

Cualquier ayuda sería genial.


Creo que la respuesta de Benjamin Gruenbaum anterior es la mejor solución para una secuencia lógica compleja, pero esta es mi alternativa para situaciones más simples. Solo uso un indicador de errorEncountered junto con return Promise.reject() para omitir cualquier declaración posterior o catch . Entonces se vería así:

let errorEncountered = false; someCall({ /* do stuff */ }) .catch({ /* handle error from someCall*/ errorEncountered = true; return Promise.reject(); }) .then({ /* do other stuff */ /* this is skipped if the preceding catch was triggered, due to Promise.reject */ }) .catch({ if (errorEncountered) { return; } /* handle error from preceding then, if it was executed */ /* if the preceding catch was executed, this is skipped due to the errorEncountered flag */ });

Si tiene más de dos pares / catch, probablemente debería usar la solución de Benjamin Gruenbaum. Pero esto funciona para una configuración simple.

Tenga en cuenta que la catch final solo tiene return; en lugar de return Promise.reject(); , porque no hay subsecuentes que necesitemos omitir, y contaría como un rechazo de Promesa no controlado, que a Node no le gusta. Como está escrito arriba, la catch final devolverá una promesa resuelta pacíficamente.


En lugar de .then().catch()... puedes hacer .then(resolveFunc, rejectFunc) . Esta cadena de promesa sería mejor si manejas las cosas en el camino. Así es como lo reescribiría:

repository.Query(getAccountByIdQuery) .then( convertDocumentToModel, () => { res.status(404).send({ error: "No account found with this Id" }); return Promise.reject(null) } ) .then( verifyOldPassword, () => Promise.reject(null) ) .then( changePassword, (error) => { if (error != null) { res.status(406).send({ OldPassword: error }); } return Promise.Promise.reject(null); } ) .then( _ => res.status(200).send(), error => { if (error != null) { console.error(error); res.status(500).send({ error: "Unable to change password" }); } } );

Nota: El if (error != null) es un truco para interactuar con el error más reciente.


Este comportamiento es exactamente como un lanzamiento sincrónico:

try{ throw new Error(); } catch(e){ // handle } // this code will run, since you recovered from the error!

Esa es la mitad del objetivo de .catch : poder recuperarse de los errores. Puede ser conveniente volver a lanzar para indicar que el estado sigue siendo un error:

try{ throw new Error(); } catch(e){ // handle throw e; // or a wrapper over e so we know it wasn''t handled } // this code will not run

Sin embargo, esto solo no funcionará en su caso ya que el error será detectado por un controlador posterior. El verdadero problema aquí es que los manejadores de errores generalizados "MANEJAR CUALQUIER COSA" son una mala práctica en general y están muy mal vistos en otros lenguajes de programación y ecosistemas. Por esta razón, Bluebird ofrece capturas tipificadas y predicadas.

La ventaja adicional es que su lógica de negocio no tiene (y no debería) tener que tener en cuenta el ciclo de solicitud / respuesta. No es responsabilidad de la consulta decidir qué estado HTTP y error obtiene el cliente y más tarde, a medida que su aplicación crece, es posible que desee separar la lógica de negocios (cómo consultar su base de datos y cómo procesar sus datos) de lo que envía al cliente (qué código de estado http, qué texto y qué respuesta).

Así es como escribiría tu código.

Primero, obtendría .Query para lanzar un NoSuchAccountError , lo subclase de Promise.OperationalError que Bluebird ya proporciona. Si no está seguro de cómo subclasificar un error, avíseme.

Además, lo subclase para AuthenticationError y luego hago algo como:

function changePassword(queryDataEtc){ return repository.Query(getAccountByIdQuery) .then(convertDocumentToModel) .then(verifyOldPassword) .then(changePassword); }

Como puede ver, está muy limpio y puede leer el texto como un manual de instrucciones de lo que sucede en el proceso. También está separado de la solicitud / respuesta.

Ahora, lo llamaría desde el controlador de ruta como tal:

changePassword(params) .catch(NoSuchAccountError, function(e){ res.status(404).send({ error: "No account found with this Id" }); }).catch(AuthenticationError, function(e){ res.status(406).send({ OldPassword: error }); }).error(function(e){ // catches any remaining operational errors res.status(500).send({ error: "Unable to change password" }); }).catch(function(e){ res.status(500).send({ error: "Unknown internal server error" }); });

De esta manera, la lógica está en un solo lugar y la decisión de cómo manejar los errores al cliente está en un solo lugar y no se abarrotan entre sí.


He estado haciendo esto:

Dejas tu captura al final. Y solo arroje un error cuando ocurra a mitad de su cadena.

repository.Query(getAccountByIdQuery) .then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error(''no_account'') .then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error(''no_account'') .then(changePassword) .then(function(){ res.status(200).send(); }) .catch((error) => { if (error.name === ''no_account''){ res.status(404).send({ error: "No account found with this Id" }); } else if (error.name === ''wrong_old_password''){ res.status(406).send({ OldPassword: error }); } else { res.status(500).send({ error: "Unable to change password" }); } });

Sus otras funciones probablemente se verían así:

function convertDocumentToModel(resultOfQuery) { if (!resultOfQuery){ throw new Error(''no_account''); } else { return new Promise(function(resolve) { //do stuff then resolve resolve(model); } }


Probablemente un poco tarde para la fiesta, pero es posible anidar .catch como se muestra aquí:

Red de desarrolladores de Mozilla: uso de promesas

Editar: envié esto porque proporciona la funcionalidad solicitada en general. Sin embargo, no lo hace en este caso particular. Porque como ya explicaron en detalle otros, se supone que .catch recupera el error. No puede, por ejemplo, enviar una respuesta al cliente en múltiples .catch llamada .catch porque un .catch sin return explícito lo resuelve con undefined en ese caso, lo que .then que se desencadene el .then procedimiento, aunque su cadena no está realmente resuelta, potencialmente provocando que se dispare un siguiente .catch y enviando otra respuesta al cliente, causando un error y probablemente arrojando un .catch de .catch en su dirección. Espero que esta oración enrevesada tenga algún sentido para ti.


.catch funciona como la instrucción try-catch , lo que significa que solo necesita un catch al final:

repository.Query(getAccountByIdQuery) .then(convertDocumentToModel) .then(verifyOldPassword) .then(changePassword) .then(function(){ res.status(200).send(); }) .catch(function(error) { if (/*see if error is not found error*/) { res.status(404).send({ error: "No account found with this Id" }); } else if (/*see if error is verification error*/) { res.status(406).send({ OldPassword: error }); } else { console.log(error); res.status(500).send({ error: "Unable to change password" }); } });