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

时间:2018-08-21 04:39:57

标签: javascript node.js express promise

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

我想做的是检查在这些过程之一之后是否引发了错误,通过发送状态代码来适当地处理它,然后完全终止承诺链。我觉得应该有一些极端清洁和简​​单的方法来执行此操作(也许是某种break语句?),但是我在任何地方都找不到它。这是我的代码。我留下评论,表明我希望在哪里中止承诺链。

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

});

4 个答案:

答案 0 :(得分:3)

您不能在中间终止承诺链。在链的后面将调用.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);
        }
    });
});

这有几个优点:

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

答案 1 :(得分:1)

.catch默认返回已解决的承诺。您需要一个被拒绝的承诺。因此,您应该从return内部.catch {em>被拒绝的承诺,以免将来的.then执行:

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

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

  

未捕获(承诺)...

因此,最好在末尾添加另一个 .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);

如果您想避免将来的所有.then和将来的.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');
  });

答案 2 :(得分:1)

一种更清洁的解决方案是使用express-validation,它是joi的简单包装,为您提供express middleware来验证身体,参数,查询,基于Joi模式的快速请求的标头和cookie。

那样,您可以简单地处理中间件在“通用”表达式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 )。

答案 3 :(得分:0)

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

类似的东西:

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()换行,但出于说明性目的将其包括在细分每个任务的一般模式及其错误处理中。