处理承诺链中的多个捕获

Gro*_*fit 118 javascript node.js promise 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" });
        });
Run Code Online (Sandbox Code Playgroud)

所以我追求的行为是:

  • 通过Id获取帐户
  • 如果此时存在拒绝,则弹出并返回错误
  • 如果没有错误,则将文档转换为模型
  • 使用数据库文档验证密码
  • 如果密码不匹配则弹出并返回不同的错误
  • 如果没有错误则更改密码
  • 然后返回成功
  • 如果出现其他任何问题,请返回500

所以,目前抓到似乎没有停止的链接,这是有道理的,所以我想知道如果有,我以某种方式迫使链停在基于错误的某一点的方式,或是否有更好的办法构造它以获得某种形式的分支行为,就像有一种情况一样if X do Y else Z.

任何帮助都会很棒.

Ben*_*aum 122

此行为与同步投掷完全相同:

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

这是一半.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
Run Code Online (Sandbox Code Playgroud)

但是,由于错误会被后来的处理程序捕获,因此单独使用此功能不适用于您的情况.这里真正的问题是,通用的"HANDLE ANYTHING"错误处理程序通常是一种不好的做法,并且在其他编程语言和生态系统中非常不受欢迎.出于这个原因,Bluebird提供了类型和谓词捕获.

增加的优势是您的业务逻辑根本不(并且不应该)必须知道请求/响应周期.查询负责决定客户端获取哪种HTTP状态和错误,以后随着应用程序的增长,您可能希望将业务逻辑(如何查询数据库以及如何处理数据)与发送给客户端的内容分开(什么http状态代码,什么文本和什么响应).

这是我编写代码的方式.

首先,我要.Query抛出一个NoSuchAccountError,我将它从Promise.OperationalErrorBluebird已经提供的子类化.如果您不确定如何将错误子类化,请告诉我.

我还要继承它AuthenticationError,然后做类似的事情:

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}
Run Code Online (Sandbox Code Playgroud)

正如您所看到的 - 它非常干净,您可以阅读文本,就像过程中发生的事情的说明手册一样.它也与请求/响应分开.

现在,我将从路由处理程序中调用它:

 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" });
 });
Run Code Online (Sandbox Code Playgroud)

这样,逻辑就在一个地方,并且如何处理客户端错误的决定都在一个地方,并且它们不会相互混乱.

  • 你可能想补充说,为某些特定错误设置中间`.catch(someSpecificError)`处理程序的原因是你想要捕获特定类型的错误(这是无害的),处理它并继续下面的流程.例如,我有一些启动代码,它们有一系列的事情要做.第一件事是从磁盘读取配置文件,但如果该配置文件正在误写这是一个OK错误(程序内置了默认值),那么我可以处理该特定错误并继续其余的流程.也可能有更好的清理,直到以后才离开. (11认同)
  • 带有ES6的@clocksmith承诺您将无法掌握所有内容并亲自手动执行“ instanceof”检查。 (3认同)
  • 我认为“这是 .catch 的一半 - 能够从错误中恢复”说得很清楚,但感谢您进一步澄清这是一个很好的例子。 (2认同)
  • 如果不使用蓝鸟怎么办?普通的 es6 Promise 仅具有传递给 catch 的字符串错误消息。 (2认同)

Esa*_*ija 46

.catch就像try-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" });
            }
        });
Run Code Online (Sandbox Code Playgroud)

  • @Grofit,它的价值 - Bluebird _were_ Petka(Esailija)的想法开始的类型捕获:)没有必要说服他,他们是一个更好的方法在这里.我认为他不想让你感到困惑,因为很多JS的人都不太了解这个概念. (8认同)
  • 是的,我知道这一点,但我不想做一个巨大的错误链,而且在需要时这样做似乎更具可读性。因此,最后是全部内容,但我喜欢输入错误的想法,因为这更能描述意图。 (2认同)

Ber*_*rgi 16

我想知道是否有办法让我以某种方式强迫链条根据错误停在某一点

不可以.你不能真正"结束"一个链,除非你抛出一个泡沫直到结束的例外.请参阅Benjamin Gruenbaum关于如何做到这一点的答案.

他的模式的推导是不区分错误类型,而是使用有错误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" });
});
Run Code Online (Sandbox Code Playgroud)


小智 7

我一直在这样做:

你最后留下你的渔获物。当它发生在你的链中途时就抛出一个错误。

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

    }
});
Run Code Online (Sandbox Code Playgroud)

您的其他功能可能如下所示:

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


tlt*_*tlt 7

可能有点晚了,但可以.catch如下所示进行嵌套:

Mozilla 开发者网络 - 使用 Promise

编辑:我提交这个是因为它通常提供了所要求的功能。但在这种特殊情况下却并非如此。因为正如其他人已经详细解释的那样,.catch应该恢复错误。例如,您不能在多个 .catch回调中向客户端发送响应,因为在这种情况下,.catch没有显式return 解析它,即使您的链没有真正解析,也会导致继续触发,可能导致后续触发并发送对客户端的另一个响应,导致错误并可能妨碍您。我希望这个复杂的句子对你来说有些道理。undefined.then.catchUnhandledPromiseRejection