Обработка нескольких уловов в цепочке обещаний

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