首页 文章

处理承诺链中的多个捕获

提问于
浏览
109

我仍然是相当新的承诺,目前正在使用蓝鸟,但我有一个场景,我不太确定如何最好地处理它 .

例如,我在快递应用程序中有一个承诺链,如下所示:

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 回答

  • 0

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

    而不是 .then().catch()... 你可以做 .then(resolveFunc, rejectFunc) . 如果您沿途处理事情,这个承诺链会更好 . 以下是我将如何重写它:

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

    注意: if (error != null) 与最近的错误进行交互有点麻烦 .

  • 118

    我一直这样做:

    你最后留下你的渔获物 . 当它发生在你的链中间时发出错误 .

    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);
        }                       
    }
    
  • 0

    我认为Benjamin Gruenbaum's answer above是复杂逻辑序列的最佳解决方案,但这是我对更简单情况的替代方案 . 我只是使用 errorEncountered 标志和 return Promise.reject() 来跳过任何后续的 thencatch 语句 . 所以它看起来像这样:

    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 */
    });
    

    如果你有两个以上/捕获对,你应该使用Benjamin Gruenbaum的解决方案 . 但这适用于简单的设置 .

    请注意,最终的 catch 只有 return; 而不是 return Promise.reject(); ,因为我们不需要跳过后续的 then ,它将被视为未处理的Promise拒绝,Node不喜欢 . 如上所述,最终 catch 将返回一个和平解决的Promise .

  • 3

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

    不,你不能真的"end"一个链,除非你抛出一个泡沫直到它结束的例外 . 有关如何操作,请参阅Benjamin Gruenbaum's answer .

    他的模式的推导不是要区分错误类型,而是使用具有 statusCodebody 字段的错误,这些字段可以从单个通用 .catch 处理程序发送 . 根据您的应用程序结构,他的解决方案可能更清晰 .

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

    是的,你可以做branching with promises . 但是,这意味着将链和"go back"保留为嵌套 - 就像在嵌套的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" });
    });
    
  • 42

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

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

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

相关问题