简体   繁体   English

如何处理错误然后立即突破承诺链?

[英]How do I handle an error and then immediately break out of a promise chain?

So I have an Express app that uses middleware to parse JSON POST requests and then populate a req.body object. 所以我有一个Express应用程序,它使用中间件来解析JSON POST请求,然后填充req.body对象。 Then I have a promise chain that validates the data against a schema using Joi, and then stores it in a database. 然后我有一个promise链,使用Joi对模式验证数据,然后将其存储在数据库中。

What I would like to do is check if an error was thrown after one of these processes, handle it appropriately by sending a status code, then COMPLETELY ABORT the promise chain. 我想要做的是检查在这些进程之一之后是否抛出错误,通过发送状态代码来适当地处理它,然后完全拒绝承诺链。 I feel like there should be some EXTREMELY CLEAN AND SIMPLE way to do this, (perhaps some sort of break statement?) but I can't find it anywhere. 我觉得应该有一些非常干净和简单的方法来做到这一点,(也许某种破坏声明?)但我无法在任何地方找到它。 Here is my code. 这是我的代码。 I left comments showing where I hope to abort the promise chain. 我留下评论显示我希望中止诺言链的地方。

const joi = require("joi");

const createUserSchema = joi.object().keys({
    username: joi.string().alphanum().min(4).max(30).required(),
    password: joi.string().alphanum().min(2).max(30).required(),
});

//Here begins my promise chain 
app.post("/createUser", (req, res) => {
    //validate javascript object against the createUserSchema before storing in database
    createUserSchema.validate(req.body)
        .catch(validationError => {
           res.sendStatus(400);

           //CLEANLY ABORT the promise chain here

           })
        .then(validatedUser => {
            //accepts a hash of inputs and stores it in a database 
            return createUser({
                    username: validatedUser.username,
                    password: validatedUser.password
                })
        .catch(error => {
            res.sendStatus(500);

            //CLEANLY ABORT the promise chain here

        })
        //Only now, if both promises are resolved do I send status 200
        .then(() => {
            res.sendStatus(200); 
            }                 
        )

});

You can't abort a promise chain in the middle. 你不能在中间中止一个承诺链。 It's going to either call a .then() or a .catch() later in the chain (assuming there are both and assuming your promises resolve or reject). 它将在链中稍后调用.catch() .then().catch() (假设有两者并假设您的承诺解决或拒绝)。

Usually, the way you handle this is you put one .catch() at the end of the chain and it examines the type of error and takes appropriate action. 通常,您处理此方法的方法是在链的末尾放置一个.catch() ,它会检查错误的类型并采取适当的操作。 You don't handle the error earlier in the chain. 您不会在链中的早期处理错误。 You let the last .catch() handle things. 你让最后一个.catch()处理事情。

Here's what I would suggest: 这是我的建议:

// helper function
function err(status, msg) {
    let obj = new Error(msg);
    obj.status = status;
    return obj;
}

//Here begins my promise chain 
app.post("/createUser", (req, res) => {
    //validate javascript object against the createUserSchema before storing in database
    createUserSchema.validate(req.body).catch(validationError => {
        throw err("validateError", 400)
    }).then(validatedUser => {
            //accepts a hash of inputs and stores it in a database 
            return createUser({
                    username: validatedUser.username,
                    password: validatedUser.password
            }).catch(err => {
                throw err("createUserError", 500);
            });
    }).then(() => {
        // success
        res.sendStatus(200); 
    }).catch(error => {
        console.log(error);
        if (error && error.status) {
            res.sendStatus(error.status);
        } else {
            // no specific error status specified
            res.sendStatus(500);
        }
    });
});

This has several advantages: 这有几个好处:

  1. Any error propagates to the last .catch() at the end of the chain where it is logged and an appropriate status is sent in just one place in the code. 任何错误都会传播到记录它的链末尾的最后一个.catch() ,并在代码中的一个位置发送适当的状态。
  2. Success is handled in just one place where that status is sent. 成功仅在发送该状态的一个地方处理。
  3. This is infinitely extensible to more links in the chain. 这对于链中的更多链接是无限可扩展的。 If you have more operations that can have errors, they can "abort" the rest of the chain (except the last .catch() by just rejecting with an appropriate error object). 如果你有更多可能有错误的操作,他们可以“中止”链的其余部分(除了最后一个.catch() ,只需拒绝一个适当的错误对象)。
  4. This is somewhat analogous to the design practice of not having lots of return value statements all over your function, but rather accumulating the result and then returning it at the end which some people consider a good practice for a complicated function. 这有点类似于在整个函数中没有大量return value语句的设计实践,而是累积结果然后在最后返回它,有些人认为这是复杂函数的良好实践。
  5. When debugging you can set breakpoints in one .then() and one .catch() to see the final resolution of the promise chain since the whole chain goes through either the last .then() or the last .catch() . 调试时,你可以在一个设置断点.then()和一个.catch()看到许链的最终解决,因为整个产业链经过要么最后.then()或最后.catch()

.catch returns a resolved Promise by default. .catch默认返回已解析的Promise You want a rejected Promsise . 你想要一个被拒绝的Promsise So, you should return a rejected promise from inside the .catch , so that future .then s won't execute: 所以,你应该return从里面被拒绝的承诺 .catch ,使未来的.then旨意不执行:

     .catch(validationError => {
       res.sendStatus(400);
       return Promise.reject();
     })

But note that this will result in a console warning: 但请注意,这将导致控制台警告:

Uncaught (in promise) ... 未捕(承诺)......

So it would be nice to add another .catch to the end, to suppress the error (as well as catch any other errors that come along): 所以最好在结尾添加另一个 .catch ,以抑制错误(以及捕获任何其他错误):

 const resolveAfterMs = ms => new Promise(res => setTimeout(() => { console.log('resolving'); res(); }), ms); console.log('start'); resolveAfterMs(500) .then(() => { console.log('throwing'); throw new Error(); }) .catch(() => { console.log('handling error'); return Promise.reject(); }) .then(() => { console.log('This .then should never execute'); }) .catch(() => void 0); 

If you want to avoid all future .then s and future .catch es, I suppose you could return a Promise that never resolves, though that doesn't really sound like a sign of a well-designed codebase: 如果你想避免未来所有.then S 未来.catch上课,我想你可以返回一个Promise ,从来没有解决,虽然这并没有真正听起来像一个精心设计的代码库的迹象:

 const resolveAfterMs = ms => new Promise(res => setTimeout(() => { console.log('resolving'); res(); }), ms); console.log('start'); resolveAfterMs(500) .then(() => { console.log('throwing'); throw new Error(); }) .catch(() => { console.log('handling error'); return new Promise(() => void 0); }) .then(() => { console.log('This .then should never execute'); }) .catch(() => { console.log('final catch'); }); 

A cleaner solution for what you are trying to accomplish might be to use express-validation , which is a simple wrapper around joi that provides you with express middleware for validation of the body, params, query, headers and cookies of an express request based on your Joi schema. 您尝试完成的更清晰的解决方案可能是使用快速验证 ,这是一个简单的joi包装器,它为您提供快速中间件,用于验证基于的快速请求的正文,参数,查询,标题和cookie。你的Joi架构。

That way, you could simply handle any Joi validation errors thrown by the middleware within your "generic" express error handler , with something like: 这样,您可以简单地处理“通用”快速错误处理程序中的中间件抛出的任何Joi验证错误,例如:

const ev = require('express-validation');
app.use(function (err, req, res, next) {
  // specific for validation errors
  if (err instanceof ev.ValidationError) 
     return res.status(err.status).json(err);
  ...
  ...
  ... 
}

If you don't want to use the express-validation package, you could write your own simple middleware that does more or less the same thing, as described here (see example here ). 如果你不希望使用快递验证包,您可以编写自己的简单的中间件,做或多或少同样的事情,如所描述这里 (见例如这里 )。

One strategy is to separate your error handling in subpromises which have their individual error handling. 一种策略是将子语句中的错误处理分开,这些子语句具有各自的错误处理。 If you throw an error from them, you'll bypass the main promise chain. 如果你从它们抛出错误,你将绕过主要的承诺链。

Something like: 就像是:

return Promise.resolve().then(() => {
  return createUserSchema.validate(req.body)
    .catch(validationError => {
       res.sendStatus(400);
       throw 'abort';
    });
}).then(validatedUser => {
   // if an error was thrown before, this code won't be executed
   // accepts a hash of inputs and stores it in a database 
   return createUser({
      username: validatedUser.username,
      password: validatedUser.password
   }).catch(error => {
      // if an error was previously thrown from `createUserSchema.validate`
      // this code won't execute

      res.sendStatus(500);
      throw 'abort';
   });
}).then(() => {
   // can put in even more code here
}).then(() => {
  // it was not aborted
  res.sendStatus(200); 
}).catch(() => {
  // it was aborted
});

You can skip the Promise.resolve().then() wrapping, but it's included for illustrative purposes of the general pattern of subdividing each task and its error handling. 您可以跳过Promise.resolve().then()换行,但它是为了说明细分每个任务及其错误处理的一般模式而包含的。

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

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