处理Promise链中的多个捕获

IT技术 javascript node.js promise bluebird
2021-01-17 01:12:53

我对 promises 还是很陌生,目前正在使用 bluebird,但是我有一个场景,我不太确定如何最好地处理它。

例如,我在一个快速应用程序中有一个Promise链,如下所示:

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

所以我所追求的行为是:

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

所以目前的捕获似乎并没有停止链接,这是有道理的,所以我想知道是否有办法让我根据错误以某种方式强制链在某个点停止,或者是否有更好的方法构造它以获得某种形式的分支行为,因为有一个if X do Y else Z.

任何帮助都会很棒。

6个回答

这种行为与同步抛出完全一样:

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

但是,仅此一项在您的情况下不起作用,因为错误会被稍后的处理程序捕获。这里的真正问题是,广义的“处理任何事情”错误处理程序通常是一种不好的做法,并且在其他编程语言和生态系统中非常不受欢迎。为此,Bluebird 提供类型化和谓词捕获。

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

这是我编写您的代码的方式。

首先,我会.Query抛出一个NoSuchAccountError,我会从Promise.OperationalErrorBluebird 已经提供的子类中继承它。如果您不确定如何对错误进行子类化,请告诉我。

我还会将其子类化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" });
 });

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

如果不使用 bluebird 怎么办?简单的 es6 Promise只有一个字符串错误消息传递给 catch。
2021-03-24 01:12:53
对于那些寻找子类化 Error 对象参考的人,请阅读 bluebirdjs.com/docs/api/catch.html#filtered-catch文章也几乎重现了此处给出的多重捕获答案。
2021-03-29 01:12:53
使用 ES6 的 @clocksmith 保证您必须instanceof自己捕获所有内容并手动进行检查。
2021-03-30 01:12:53
您可能想补充一点,.catch(someSpecificError)为某些特定错误设置中间处理程序的原因是,如果您想捕获特定类型的错误(无害),处理它并继续接下来的流程。例如,我有一些启动代码有一系列的事情要做。第一件事是从磁盘读取配置文件,但如果该配置文件丢失,那就是一个 OK 错误(程序已内置默认值),因此我可以处理该特定错误并继续流程的其余部分。也可能有更好的清理工作,不要在以后离开。
2021-04-10 01:12:53
我认为“这是 .catch 的一半 - 能够从错误中恢复”说得很清楚,但感谢您进一步澄清这是一个很好的例子。
2021-04-10 01:12:53

.catchtry-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" });
            }
        });
是的,我知道这一点,但我不想做一个巨大的错误链,而且在需要时这样做似乎更具可读性。因此,最后一应俱全,但我喜欢输入错误的想法,因为它更能描述意图。
2021-03-19 01:12:53
@Grofit 的value - 在 Bluebird输入的捕获物Petka (Esailija) 的想法:) 无需说服他,他们在这里是一种更可取的方法。我认为他不想让您感到困惑,因为 JS 中的很多人都不太了解这个概念。
2021-03-22 01:12:53

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

不。你不能真正“结束”一个链,除非你抛出一个一直冒泡到结束的异常。有关如何执行此操作,请参阅Benjamin Gruenbaum 的回答

他的模式的推导是不区分错误类型,而是使用有错误statusCode,并body可以从一个单一的,通用的发送场.catch处理。不过,根据您的应用程序结构,他的解决方案可能更简洁。

或者如果有更好的方法来构造它以获得某种形式的分支行为

是的,您可以使用 promise进行分支但是,这意味着离开链并“返回”嵌套 - 就像您在嵌套的 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" });
});

我一直在这样做:

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

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

    }
});

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

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

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

Mozilla 开发者网络 - 使用 Promises

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

@AntonMenshov 你是对的。我扩展了我的答案,解释了为什么嵌套仍然无法实现他想要的行为
2021-03-23 01:12:53