Обработка нескольких уловов в цепочке обещаний
95 Grofit [2014-09-27 19:02:00]
Я до сих пор довольно новичок в promises и сейчас использую bluebird, однако у меня есть сценарий, в котором я не совсем уверен, как наилучшим образом справиться с этим.
Так, например, у меня есть цепочка обещаний в экспресс-приложении, например:
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" });
});
Итак, поведение, за которым я следую, следующее:
- Идет, чтобы получить учетную запись по идентификатору
- Если в этот момент есть отклонение, выполните вскрытие и верните ошибку.
- Если нет ошибки, конвертируйте документ, возвращенный в модель
- Проверьте пароль с документом базы данных
- Если пароли не совпадают, тогда вылетают и возвращают другую ошибку.
- Если нет ошибки, пароли
- Затем верните успех
- Если что-то еще пошло не так, верните 500
Таким образом, в настоящее время уловы, похоже, не останавливают цепочку, и это имеет смысл, поэтому мне интересно, есть ли способ заставить меня каким-то образом заставить цепочку остановиться в определенной точке на основе ошибок или если есть лучший способ структурировать это, чтобы получить некоторую форму поведения ветвления, так как существует случай if X do Y else Z.
Любая помощь будет большой.
javascript node.js promise bluebird
3 ответа
110 Решение Benjamin Gruenbaum [2014-09-27 21:06:00]
Это похоже на синхронный бросок:
try{
throw new Error();
} catch(e){
// handle
}
// this code will run, since you recovered from the error!
Эта половина точки .catch - для восстановления после ошибок. Возможно, было бы желательно перебросить сигнал, чтобы состояние оставалось ошибкой:
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
Однако, это само по себе не будет работать в вашем случае, так как ошибка будет улавливаться более поздним обработчиком. Реальная проблема здесь заключается в том, что обобщенные обработчики ошибок "HANDLE ANYTHING" являются плохой практикой в целом и чрезвычайно недовольны другими языками программирования и экосистемами. По этой причине Bluebird предлагает типизированные и предикатные уловы.
Дополнительное преимущество заключается в том, что ваша бизнес-логика не должна (и не должна) знать о цикле запроса/ответа вообще. Ответ на вопрос не зависит от того, какой HTTP-статус и ошибка клиент получает, а позже по мере роста вашего приложения вы можете отделить бизнес-логику (как запросить свою БД и как обрабатывать свои данные) от того, что вы отправляете клиенту (какой код статуса http, какой текст и какой ответ).
Вот как я напишу ваш код.
Во-первых, я бы получил .Query, чтобы выбросить NoSuchAccountError, я бы подклассировал его из Promise.OperationalError, который Bluebird уже предоставляет. Если вы не знаете, как подклассировать ошибку, дайте мне знать.
Я дополнительно подклассифицировал его для AuthenticationError, а затем сделаю что-то вроде:
function changePassword(queryDataEtc){
return repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword);
}
Как вы можете видеть - он очень чистый, и вы можете прочитать текст, как руководство по эксплуатации того, что происходит в процессе. Он также отделен от запроса/ответа.
Теперь я бы назвал это из обработчика маршрута как такового:
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" });
});
Таким образом, логика находится в одном месте, и решение о том, как обрабатывать ошибки для клиента, находится в одном месте, и они не мешают друг другу.
35 Esailija [2014-09-27 20:59:00]
.catch работает как оператор try-catch, что означает, что вам нужно только один catch в конце:
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" });
}
});
14 Bergi [2014-09-27 21:35:00]
Мне интересно, есть ли способ заставить меня каким-то образом заставить цепочку остановиться в определенной точке на основе ошибок
Нет. Вы не можете "закончить" цепочку, если только вы не выбросите исключение, которое пузырится до конца. См. ответ Бенджамина Грюнбаума о том, как это сделать.
Вывод его шаблона будет заключаться не в том, чтобы отличать типы ошибок, а за использование ошибок, которые имеют поля statusCode и body, которые могут быть отправлены из одного, общего .catch обработчика. В зависимости от вашей структуры приложения его решение может быть более чистым, хотя.
или если есть лучший способ структурировать это, чтобы получить некоторую форму поведения ветвления
Да, вы можете сделать ветвление с promises. Тем не менее, это означает, что вы оставите цепочку и "вернитесь" к вложенности - точно так же, как вы делали бы в вложенном выражении if-else или 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" });
});