从Node.js中的端点代码中提取异步函数

问题描述

为了重组代码,我试图将一部分代码放在端点代码之外。

当前有效的方法

router.post('/',async (req,res) => {
    try {
        const userExists = await User.findOne({email: req.body.email})
        if (userExists) {
            return res.status(400).send('User already exists')
        }        
    } catch (error) {
        return res.status(500).send('Internal server error')
    } // stops here if a user does exist into database
    console.log('do next stuff');
...
})

我想做的是将这个try/catch块放在一个单独的函数中:

async function checkUserExistence(req,res) {
    try {
        const userExists = await User.findOne({email: req.body.email});
        if (userExists) {
            return res.status(400).send('User already exists');
        }        
    } catch (error) {
        return res.status(500).send('Internal server error');
    }
}

然后,我简单地调用函数

router.post('/',res) => {
    checkUserExistence(req,res) // does not stop here if a user does exist into database
    console.log('do next stuff');
...
})

这样做,我仍然得到响应用户已经存在,但是代码继续在调用函数中执行,因为return res.status(400).send('User already exists')行仅适用于被调用函数

请问该如何解决?谢谢您的帮助。

解决方法

从我的角度来看,在重构范围内,您需要将业务逻辑移出控制器。与模型相关的功能对请求或响应一无所知,您只需要向它们提供直接需要的数据即可。

checkUserExistence函数应仅在用户存在或不存在时通知您

function checkUserExistence(body) {
    return User.findOne({email: body.email});
}

并且控制器需要通过响应进行管理

router.post('/',async (req,res) => {
    let userExists;
    try {
      userExists = await checkUserExistence(req.body)
    } catch (e) {
      return res.status(500).send('Internal server error');
    }
    if (userExists) {
      return res.status(400).send('User already exists');
    } 
})
,

问题是您需要等待Promise解决,并知道是否设置了res

解决方案1-有趣的时光

async function checkUserExistence(req,res) {
    let isExsist = false;
    try {
        const userExists = await User.findOne({email: req.body.email});
        if (userExists) {
           res.status(400).send('User already exists');
           isExsist = true;
        }        
    } catch (error) {
       res.status(500).send('Internal server error');
       isExsist = true;
    }

   return isExsist;
}
router.post('/',res) => {
    const isExsist = await checkUserExistence(req,res)

    if(isExsist){
       return res;
    }
    console.log('do next stuff');
    ...
})

这将起作用,但是这种方法存在问题。主要是这样的事实:checkUserExistence控制着req并返回bool只是为了承认req已经知道的事情。

解决方案2-分离关注点

我们可以重构使代码更加简化,并使其具有定义明确的API。

首先,让我们从req中删除rescheckUserExistence。不需要他们。现在我们知道checkUserExistence应该收到email并应该引发两种类型的错误(DB错误和USER_ALREADY_EXSIST)。

async function checkUserExistence(email) {
    const userExists = await User.findOne({ email: email });
    if(userExists) {
        throw new Error('ERROR::USER_ALREADY_EXSIST');
    }
}

此功能是Business Layer的一部分。这意味着它必须Controller Layer无关。 关于此方法的一个很好的想法是,您现在可以在没有express的情况下使用此方法。

第2步是将此Business Layer方法与控制器一起使用。

router.post('/',res) => {

    try {
      await checkUserExistence(req.body.email);
      res.send("not exist"); // or do stuff

    } catch (e) {
      switch(e.message) {
         case "ERROR::USER_ALREADY_EXSIST": res.status(400).send('User already exists'); brake;
         default:
            res.status(500).send('Internal server error'); brake;
      }
    }
})

现在我们可以看到HTTP在做什么。请注意,在default情况下(500),数据库抛出的所有异常都将得到解决。

我建议您深入研究express中间件错误处理。它可以减少通用错误的代码重复。参见:https://expressjs.com/en/guide/error-handling.html