简体   繁体   English

处理承诺链中的多个渔获

[英]Handling multiple catches in promise chain

I am still fairly new to promises and am using bluebird currently, however I have a scenario where I am not quite sure how to best deal with it. 我对诺言仍然还很陌生,目前正在使用蓝鸟,但是在我不确定如何最好地处理它的情况下。

So for example I have a promise chain within an express app like so: 因此,举例来说,我在Express应用程序中有一个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" });
        });

So the behaviour I am after is: 所以我的行为是:

  • Goes to get account by Id 通过ID获取帐户
  • If there is a rejection at this point, bomb out and return an error 如果此时存在拒绝,请轰炸并返回错误
  • If there is no error convert the document returned to a model 如果没有错误,则将文档转换为模型
  • Verify the password with the database document 使用数据库文档验证密码
  • If the passwords dont match then bomb out and return a different error 如果密码不匹配,则炸开并返回其他错误
  • If there is no error change the passwords 如果没有错误,请更改密码
  • Then return success 然后返回成功
  • If anything else went wrong, return a 500 如果有其他问题,请返回500

So currently catches do not seem to stop the chaining, and that makes sense, so I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors, or if there is a better way to structure this to get some form of branching behaviour, as there is a case of if X do Y else Z . 因此,当前的捕获量似乎并没有停止链接,这是有道理的,所以我想知道是否存在一种方法可以根据错误以某种方式强制链停止在某个点,或者是否有更好的方法构造这种结构以获得某种形式的分支行为,例如, if X do Y else Z

Any help would be great. 任何帮助都会很棒。

This behavior is exactly like a synchronous throw: 此行为完全类似于同步抛出:

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

That's half of the point of .catch - to be able to recover from errors. 这就是.catch的一半-能够从错误中恢复。 It might be desirable to rethrow to signal the state is still an error: 可能需要重新抛出信号以指示状态仍然是错误:

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

However, this alone won't work in your case since the error be caught by a later handler. 但是,仅此一种方法对您而言不起作用,因为该错误将由以后的处理程序捕获。 The real issue here is that generalized "HANDLE ANYTHING" error handlers are a bad practice in general and are extremely frowned upon in other programming languages and ecosystems. 真正的问题是,广义的“ HANDLE ANYTHING”错误处理程序通常是一种不好的做法,在其他编程语言和生态系统中却极少使用。 For this reason Bluebird offers typed and predicate catches. 因此,蓝鸟提供了类型化和谓词性的捕获。

The added advantage is that your business logic does not (and shouldn't) have to be aware of the request/response cycle at all. 附加的优点是您的业务逻辑根本不需要(也不应该)知道请求/响应周期。 It is not the query's responsibility to decide which HTTP status and error the client gets and later as your app grows you might want to separate the business logic (how to query your DB and how to process your data) from what you send to the client (what http status code, what text and what response). 决定客户端获取哪种HTTP状态和错误不是查询的责任,而随着应用的增长,您可能希望将业务逻辑(如何查询数据库以及如何处理数据)与发送给客户端的内容分开(什么http状态代码,什么文本和什么响应)。

Here is how I'd write your code. 这是我编写您的代码的方式。

First, I'd get .Query to throw a NoSuchAccountError , I'd subclass it from Promise.OperationalError which Bluebird already provides. 首先,我得到.Query抛出NoSuchAccountError ,我将从Bluebird已经提供的Promise.OperationalError它。 If you're unsure how to subclass an error let me know. 如果您不确定如何将错误归类,请告诉我。

I'd additionally subclass it for AuthenticationError and then do something like: 我还要为AuthenticationError子类化它,然后做类似的事情:

function changePassword(queryDataEtc){ 
    return repository.Query(getAccountByIdQuery)
                     .then(convertDocumentToModel)
                     .then(verifyOldPassword)
                     .then(changePassword);
}

As you can see - it's very clean and you can read the text like an instruction manual of what happens in the process. 如您所见-它非常干净,您可以阅读文本,就像使用说明书一样,了解过程中发生的情况。 It is also separated from the request/response. 它也与请求/响应分开。

Now, I'd call it from the route handler as such: 现在,我将从路由处理程序中这样调用它:

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

This way, the logic is all in one place and the decision of how to handle errors to the client is all in one place and they don't clutter eachother. 这样,逻辑就全部集中在一个地方,而如何为客户处理错误的决定就都集中在一个地方,而且它们不会相互干扰。

.catch works like the try-catch statement, which means you only need one catch at the end: .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" });
            }
        });

I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors 我想知道是否存在一种方法可以根据错误以某种方式强制链条停止在某个点

No. You cannot really "end" a chain, unless you throw an exception that bubbles until its end. 不可以。除非您抛出一个冒泡直到结束的异常,否则您无法真正“终止”一条链。 See Benjamin Gruenbaum's answer for how to do that. 有关如何执行此操作的信息,请参见本杰明·格伦鲍姆(Benjamin Gruenbaum)的答案

A derivation of his pattern would be not to distinguish error types, but use errors that have statusCode and body fields which can be sent from a single, generic .catch handler. 他的模式的派生将不是区分错误类型,而是使用具有statusCodebody字段的错误,这些错误可以从单个通用.catch处理程序发送。 Depending on your application structure, his solution might be cleaner though. 根据您的应用程序结构,他的解决方案可能会更干净。

or if there is a better way to structure this to get some form of branching behaviour 或者是否有更好的方法来构造它以获得某种形式的分支行为

Yes, you can do branching with promises . 是的,您可以使用promises进行分支 However, this means to leave the chain and "go back" to nesting - just like you'd do in an nested if-else or try-catch statement: 但是,这意味着离开链并“返回”嵌套-就像您在嵌套的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" });
});

I have been doing this way: 我一直在这样做:

You leave your catch in the end. 最终您将捕获到的东西。 And just throw an error when it happens midway your chain. 当链中途发生错误时,就抛出一个错误。

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

    }
});

Your other functions would probably look something like this: 您的其他函数可能看起来像这样:

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

Probably a little late to the party, but it is possible to nest .catch as shown here: 派对可能要.catch ,但是可以嵌套.catch ,如下所示:

Mozilla Developer Network - Using Promises Mozilla开发人员网络-使用承诺

Edit: I submitted this because it provides the asked functionality in general. 编辑:我提交了此内容,因为它通常提供所需的功能。 However it doesn't in this particular case. 但是,在这种特殊情况下却不是。 Because as explained in detail by others already, .catch is supposed to recover the error. 因为正如其他人已经详细解释的那样, .catch应该可以恢复该错误。 You can't, for example, send a response to the client in multiple .catch callbacks because a .catch with no explicit return resolves it with undefined in that case, causing proceeding .then to trigger even though your chain is not really resolved, potentially causing a following .catch to trigger and sending another response to the client, causing an error and likely throwing an UnhandledPromiseRejection your way. 例如,您不能在多个 .catch回调中向客户端发送响应,因为没有显式return.catch在这种情况下会使用undefined解析它,从而导致继续进行.then触发,即使您的链没有真正解决,可能导致随后的.catch触发并向客户端发送另一个响应,从而导致错误并可能以您的方式抛出UnhandledPromiseRejection I hope this convoluted sentence made some sense to you. 我希望这个复杂的句子对您有意义。

Instead of .then().catch()... you can do .then(resolveFunc, rejectFunc) . 代替.then().catch()...您可以执行.then(resolveFunc, rejectFunc) This promise chain would be better if you handled things along the way. 如果您一路处理事情,那么这个承诺链会更好。 Here is how I would rewrite it: 这是我将其重写的方式:

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

Note: The if (error != null) is a bit of a hack to interact with the most recent error. 注意: if (error != null)与最近的错误交互有点麻烦。

I think Benjamin Gruenbaum's answer above is the best solution for a complex logic sequence, but here is my alternative for simpler situations. 我认为本杰明·格鲁恩鲍姆(Benjamin Gruenbaum)的上述回答是复杂逻辑序列的最佳解决方案,但是对于更简单的情况,这是我的选择。 I just use an errorEncountered flag along with return Promise.reject() to skip any subsequent then or catch statements. 我只使用errorEncountered标志和return Promise.reject()来跳过任何后续的thencatch语句。 So it would look like this: 所以它看起来像这样:

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

If you have more than two then/catch pairs, you should probably use Benjamin Gruenbaum's solution. 如果您有两个以上的对/捕获对,则可能应该使用本杰明·格伦鲍姆的解决方案。 But this works for a simple set-up. 但这适用于简单的设置。

Note that the final catch only has return; 注意,最后的catch只有return; rather than return Promise.reject(); 而不是return Promise.reject(); , because there's no subsequent then that we need to skip, and it would count as an unhandled Promise rejection, which Node doesn't like. ,因为没有后续的then我们需要跳过,而这只能算作一个未处理的承诺拒绝,该节点不喜欢。 As is written above, the final catch will return a peacefully resolved Promise. 如上所述,最后的catch将返回一个和平解决的承诺。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM