以快递方式发送错误时防止代码重复的最有效方法

Posted

技术标签:

【中文标题】以快递方式发送错误时防止代码重复的最有效方法【英文标题】:Most efficient way to prevent code duplication when sending errors in express 【发布时间】:2018-09-19 12:53:28 【问题描述】:

我正在用我的 express 应用程序编写一个登录函数,不喜欢在回调链中重复很多 res.status(500).send(body) 的事实:

router.post('/login', (req, res) => 

  User.findOne( 
    where:  username: req.body.username  
  )
    .then( user => 
      if (user) 
        User.verifyPassword(req.body.password, user)
          .then((verified) => 
            if (verified) 
              let signedToken = jwt.sign(
                 user: user.id ,
                'secret',
                 expiresIn: 24 * 60 * 60 
              );

              res.status(200).send(
                token: signedToken,
                userId:  user.id,
                username: user.username 
              );
             else 
              // If password entered does not match user password 
              res.status(500).send( error: true, );
            
          )
          // If bycrpt explodes
          .catch((error) => 
            res.status(500).send( error: error, );
          );
       else 
        // If we can't even find a user with that username
        res.status(500).send( error: true, );
      
    )
    // If the db query to find a user explodes
    .catch(error => 
      res.status(500).send( error: error );
    );
);

其中两个与导致 API 崩溃的模糊异常有关。其他两个基于 API 返回的布尔值。 我不是一个后端工程师,这只是一个个人项目,但我想知道 Node.js 世界中的最佳实践是什么。

虽然我们这样做了,但我不确定在这些错误情况下发送的适当状态代码是什么,因为我确信 500 是不正确的。

【问题讨论】:

【参考方案1】:

我会像这样重写你的代码,我们只有一个.catch

router.post('/login', (req, res) => 

    User.findOne(  where:  username: req.body.username )
        .then(user => 

            if (!user) // If we can't even find a user with that username
                return Promise.reject(true); // You should probably return an Error

            return User.verifyPassword(req.body.password, user)

        )
        .then((verified) => 

            if (!verified) // If password entered does not match user password 
                return Promise.reject(true); // You should probably return an Error

            let signedToken = jwt.sign(
                    user: user.id
                ,
                'secret', 
                    expiresIn: 24 * 60 * 60
                
            );

            res.status(200).send(
                token: signedToken,
                userId: user.id,
                username: user.username
            );

        ).catch(error => 

            // If the db query to find a user explodes
            // If we can't even find a user with that username
            // If password entered does not match user password             

            // You could throw different errors and handle
            // all of them differently here
            res.status(500).send(
                error: error
            );
        );
);

这可以进一步改进,使用async/await

router.post('/login', async(req, res) => 

    try 

        const user = await User.findOne(   where:  username: req.body.username );

        if (!user) // If we can't even find a user with that username
            throw new Error('Invalid username');

        const verified = await User.verifyPassword(req.body.password, user)

        if (!verified) // If password entered does not match user password 
            throw new Error('Invalid password');

        let signedToken = jwt.sign(
                user: user.id
            ,
            'secret', 
                expiresIn: 24 * 60 * 60
            
        );

        res.status(200).send(
            token: signedToken,
            userId: user.id,
            username: user.username
        );

     catch(error) 

        // If the db query to find a user explodes
        // If we can't even find a user with that username
        // If password entered does not match user password             
        res.status(500).send(
            error: error.message
        );

    

);

关于状态码,有多种处理方式,我通常为每个状态码抛出一个特定的错误。

errors.js

class Unauthorized extends Error 

    constructor(message) 
        super(message);     
        this.name = 'UnauthorizedError';
        this.statusCode = 401
    


class BadRequest extends Error 

    constructor(message) 
        super(message);     
        this.name = 'BadRequestError';
        this.statusCode = 400
    


/** more errors... **/

module.exports = 
    Unauthorized,
    BadRequest
;

所以我们现在可以设置正确的状态码了:

const  Unauthorized  = require('./errors');
/* ... */

try 

    /* ... */
    if (!verified) // Some people may say 422 status code... 
        throw new Unauthorized('Invalid password');

    /* ... */
 catch(error) 

    res.status(error.statusCode || 500).send(
        error: error.message
    );


虽然我们正在处理它,但我不确定合适的状态代码是什么 在这些错误情况下发送将是,因为我确信 500 不正确。

你说得对,为每个错误设置 500 是不正确的。我会留下几个问题,可能会帮助您设置正确的状态代码,因为在这个问题中讨论它太长了。

What's an appropriate HTTP status code to return by a REST API service for a validation failure?

What's the appropriate HTTP status code to return if a user tries logging in with an incorrect username / password, but correct format?

【讨论】:

Object.entries(require('http').STATUS_CODES).filter(([statusCode]) => statusCode >= 400).forEach(([statusCode, message]) => const name = message.replace(/\W+/g, ''); exports[name] = class extends Error constructor () super(message); this.name = name; this.statusCode = Number(statusCode); ; ); 是一个简洁的小 sn-p,可以放入 errors.js 模块。 @marcoscasagrande 如何使这些错误类扩展在我的应用程序中随处可见?为了更好地分离关注点,我将路由分布在许多文件中,然后将它们合并到一个文件中。如何让这些错误扩展在全球范围内可见? @MarcFletcher 更新了我的答案!只需将它们放在错误类中即可。我还修复了一个小错误,我在抛出Unauthorized时添加了new关键字,当我回答时已经深夜了,它滑倒了:)

以上是关于以快递方式发送错误时防止代码重复的最有效方法的主要内容,如果未能解决你的问题,请参考以下文章

以快递方式从 POST 请求处理程序发送 WebSocket 消息

电商电子面单设置-快递鸟API接口

防止重放攻击最有效的方法是

使用返回值以快递方式发送到ajax成功

无头件是啥意思

在Java中检测偶数的最有效方法是什么? [重复]