Javascript 处理承诺链中的多个捕获

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26076511/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-22 22:18:32  来源:igfitidea点击:

Handling multiple catches in promise chain

javascriptnode.jspromisebluebird

提问by Grofit

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.

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

So for example I have a promise chain within an express app like so:

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

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

任何帮助都会很棒。

回答by Benjamin Gruenbaum

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. It might be desirable to rethrow to signal the state is still an 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

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. For this reason Bluebird offers typed and predicate catches.

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

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 .Queryto throw a NoSuchAccountError, I'd subclass it from Promise.OperationalErrorwhich Bluebird already provides. If you're unsure how to subclass an error let me know.

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

I'd additionally subclass it for AuthenticationErrorand 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.

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

回答by Esailija

.catchworks like the try-catchstatement, which means you only need one catch at the end:

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

回答by Bergi

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 answerfor how to do that.

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

A derivation of his pattern would be not to distinguish error types, but use errors that have statusCodeand bodyfields which can be sent from a single, generic .catchhandler. Depending on your application structure, his solution might be cleaner though.

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

or if there is a better way to structure this to get some form of branching behaviour

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

Yes, you can do branching with 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:

是的,您可以使用 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" });
});

回答by Leo Leao

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

回答by denkquer

Probably a little late to the party, but it is possible to nest .catchas shown here:

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

Mozilla Developer Network - Using Promises

Mozilla 开发者网络 - 使用 Promises

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, .catchis supposed to recover the error. You can't, for example, send a response to the client in multiple.catchcallbacks because a .catchwith no explicit returnresolvesit with undefinedin that case, causing proceeding .thento trigger even though your chain is not really resolved, potentially causing a following .catchto trigger and sending another response to the client, causing an error and likely throwing an UnhandledPromiseRejectionyour way. I hope this convoluted sentence made some sense to you.

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

回答by mvndaai

Instead of .then().catch()...you can do .then(resolveFunc, rejectFunc). This promise chain would be better if you handled things along the way. Here is how I would rewrite it:

而不是.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" });
            }
        }
    );

Note:The if (error != null)is a bit of a hack to interact with the most recent error.

注:if (error != null)是一个黑客位的与最新的错误互动。

回答by temporary_user_name

I think Benjamin Gruenbaum's answer aboveis the best solution for a complex logic sequence, but here is my alternative for simpler situations. I just use an errorEncounteredflag along with return Promise.reject()to skip any subsequent thenor catchstatements. So it would look like this:

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

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.

如果您有两个以上的 then/catch 对,您可能应该使用 Benjamin Gruenbaum 的解决方案。但这适用于简单的设置。

Note that the final catchonly has return;rather than return Promise.reject();, because there's no subsequent thenthat we need to skip, and it would count as an unhandled Promise rejection, which Node doesn't like. As is written above, the final catchwill return a peacefully resolved Promise.

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