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