如何处理错误然后立即脱离承诺链?

Posted

技术标签:

【中文标题】如何处理错误然后立即脱离承诺链?【英文标题】:How do I handle an error and then immediately break out of a promise chain? 【发布时间】:2019-01-27 05:27:28 【问题描述】:

所以我有一个 Express 应用程序,它使用中间件解析 JSON POST 请求,然后填充 req.body 对象。然后我有一个承诺链,它使用 Joi 根据模式验证数据,然后将其存储在数据库中。

我想要做的是检查在这些过程之一之后是否引发了错误,通过发送状态代码适当地处理它,然后完全中止承诺链。我觉得应该有一些非常干净和简单的方法来做到这一点,(也许是某种中断声明?)但我在任何地方都找不到。这是我的代码。我让 cmets 显示了我希望中止承诺链的位置。

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

);

【问题讨论】:

【参考方案1】:

一个更简洁的解决方案可能是使用express-validation,它是joi 的简单包装,为您提供express middleware 用于验证正文、参数、查询、标题和cookie基于您的 Joi 架构的快速请求。

这样,您可以简单地处理中间件在您的“通用”快递error handler 中抛出的任何 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);
  ...
  ...
  ... 

如果您不想使用express-validation 包,您可以编写自己的简单中间件,其功能或多或少与here 所述相同(参见示例here)。

【讨论】:

谢谢,我试试那个。【参考方案2】:

你不能在中间中止一个承诺链。它将在链的后面调用.then().catch()(假设两者都有,并且假设您的承诺解决或拒绝)。

通常,您处理此问题的方式是将一个.catch() 放在链的末尾,它会检查错误的类型并采取适当的措施。您不处理链中较早的错误。你让最后一个.catch()处理事情。

以下是我的建议:

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

这有几个优点:

    任何错误都会传播到链末尾的最后一个.catch(),并在此记录它,并且仅在代码中的一个位置发送适当的状态。 成功仅在发送该状态的地方处理。 这可以无限扩展到链中的更多链接。如果您有更多可能出现错误的操作,它们可以“中止”链的其余部分(最后一个 .catch() 除外,只需使用适当的错误对象进行拒绝)。 这有点类似于在你的函数中没有大量 return value 语句的设计实践,而是累积结果然后在最后返回它,有些人认为这是复杂函数的良好实践。 调试时,您可以在一个.then() 和一个.catch() 中设置断点,以查看承诺链的最终解决方案,因为整个链通过最后一个.then() 或最后一个.catch()

【讨论】:

我如何才能准确地确定.catch 语句引发的错误类型? @JoshuaAvery - 你研究过我的代码建议吗?它处理错误,设置它希望如何处理错误,然后重新抛出错误,以便跳转到最后一个.catch(),状态和日志消息已经设置。那是一种技术。另一种是为每个地方抛出一个单独的错误类型,然后在.catch() 中使用switch 语句来检查每种类型的错误。我更喜欢这种机制,因为我认为它可以更好地扩展,并将一个操作的所有代码放在一起。 返回带有.catch 的createUser 承诺是否会将.catch 暴露在链上进一步引发的错误?换句话说,返回的.catch 不会捕获上面抛出的错误吗? 如果我误解了这些概念,我深表歉意,但 node 对我来说相对较新。 没关系,我现在明白了! then 语句将不会执行并返回.catch,因为抛出的错误将一直跳到最后的.catch 语句。非常感谢!【参考方案3】:

一种策略是将您的错误处理分离到具有各自错误处理的子承诺中。如果你从他们那里抛出一个错误,你将绕过主要的承诺链。

类似:

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

您可以跳过 Promise.resolve().then() 包装,但包含它是为了说明细分每个任务的一般模式及其错误处理。

【讨论】:

【参考方案4】:

.catch 默认返回一个已解决的 Promise。你想要一个被拒绝的承诺。所以,你应该从.catch 内部return 一个rejected promise,这样未来的.thens 就不会执行:

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

但请注意,这将导致控制台警告:

未捕获(承诺)...

所以最好在末尾添加 another .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);

如果你想避免所有未来的.thens 未来的.catches,我想你可以返回一个永远不会解决的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');
  );

【讨论】:

是的,但是另一个.catch 最终也会遇到这个错误,这是一个问题。另一个.catch 最终会赶上promise.reject 吗? 抱歉,澄清一下,我希望 .catch 完全中止链,绕过所有其他 .catch @JoshuaAvery 查看编辑。我想你可以返回一个永远不会解决的Promise,确保链永远不会进展,虽然我不知道是否有更优雅的解决方案 @CertainPerformance 那么我可以使用另一种解决方案吗?我可以通过某种方式处理多个可能的拒绝或错误并以不同的方式回应它们?

以上是关于如何处理错误然后立即脱离承诺链?的主要内容,如果未能解决你的问题,请参考以下文章

expressjs 承诺和 sunc 错误处理

处理 Mongoose 验证错误——在哪里以及如何处理?

如何处理访问写入冲突错误? [复制]

使用 fetch 和 ES6 承诺处理自定义错误的最简洁方法

Java:如何处理错误的方法参数?

如何处理错误?