有没有更好的方法来处理Node.js / Expressjs中的嵌套回调?

时间:2012-12-23 05:39:58

标签: javascript node.js architecture callback express

我使用Node.js和Expressjs作为服务器端编码,使用MongoDB作为后端。我是所有这些技术的新手。 我需要根据请求做一系列操作。

例如在用户管理中

  1. 检查已注册的用户
  2. 如果已注册,请重新发送激活电子邮件
  3. 如果没有注册,那么从另一个表中获取userId,该表将维护用户,资产等的ID。[我知道MongoDB提供了唯一的_id;但我需要一个唯一的整数id作为userId]
  4. 创建用户
  5. 发送成功或失败回复。
  6. 为了实现这一点,我编写了以下代码:

    exports.register = function(req,res,state,_this,callback) {
       switch(state) {
        case 1: //check user already registered or not
          _this.checkUser(req, res, ( state + 1 ), _this, _this.register, callback);
          break;
        case 2: //Already registered user so resend the activation email
          _this.resendEmail(req, res, 200, _this, _this.register, callback);
          break;
        case 3: //not registered user so get the userId from another table that will maintain the ids for user,assets etc
          _this.getSysIds(req, res, ( state + 2 ), _this, _this.register, callback);
          break;
        case 4: //create the entry in user table
          _this.createUser(req, res, ( state + 1 ), _this, _this.register, callback);
          break;
        case 200: //Create Success Response
          callback(true);
          break;
        case 101://Error
          callback(false);
          break;
        default:
          callback(false);
        break;
      }
    };
    

    检查用户代码是这样的

    exports.checkUser = function(req,res,state,_this,next,callback) {
        //check user already registered or not
        if(user) {//Already registered user so resend the activation email
           next(req,res,state,_this,callback);
        }
        else {//not registered user so get the userId
          next(req,res,(state + 1),_this,callback);
        }
    }
    

    与其他功能类似。

    第一次调用register函数将从app.get执行

    user.register(req,res,1,this,function(status) {
    //do somthing
    });
    

    有没有更好的方法呢?我面临的问题是基于一些条件我必须遵循一系列行动。我可以在嵌套的回调结构中编写所有这些,但在这种情况下,我无法重用我的代码。

    我的老板告诉我的一个问题是,我在代码中调用函数寄存器并将其放入一堆回调中

    州1:chekuser

    州3:getIds

    状态4:创建用户

    最后在200州,我刚从堆栈退出?它可能导致堆栈溢出!

    有没有更好的方法来处理Node.js / Expressjs中的回调?

    注意:上面是一个示例代码。我有很多不同的情况。

2 个答案:

答案 0 :(得分:3)

也许更优雅的方法就是将所有各种可能的功能放入一个对象中,并根据您所处的状态调用您需要的任何一个。有点像

var States = {
   1: checkuser,
   2: resendEmail,
   3: getSysIds,
   4: createUser,
   5: whateverIsNext 
}

然后你必须拥有一个能完成所有事情的功能

function exec(req,res,state,_this,callback){
   States[state].apply(this, arguments) 
}

你的每个函数都只是将state参数设置为它需要的东西,然后召回exec,例如

function checkUser(req,res,state,_this,callback){
    var doneRight = do_whatever_you_need;
    state = doneRight? state++: state; //just set state to whatever number it should be
    exec(req,res,state,_this,callback);
}

通过这种方式,您可以获得良好且可读/可更改的步骤列表。你有一个执行所有功能的功能,每个步骤都必须管理下一步应该是什么。如果您愿意,还可以使用named替换带编号的步骤,这样可以使其更具可读性,但是您将无法增加/减少步进变量。

答案 1 :(得分:2)

你在这里过度设计混合中间件,数据库层和路由处理程序。让它变得更简单,更可重复使用。

首先,让我们确保我们的中间件和路由处理程序可以相互通信。这可以通过session middleware完成:

// Somewhere inside configuration:
app.use(express.cookieParser()
app.use(express.session({secret: 'cookie monster'));

// If you don't want sessions, change with this:
app.use(function (req, res, next) {
  req.session = {};
  next();
});

您现在想要的是在请求处理期间将用户信息放在任何地方。现在,它只是电子邮件,是否注册。那将是第一个独立的中间件。为简洁起见,我们假设您通过query stringemail查询数据库来检测用户是否已注册。

// Fills `req.session.user` with user info (email, registration). 
function userInfo (req, res, next) {
  var user = req.session.user = req.session.user || {};
  user.email = req.query.email;  // I'll skip validation.

  // I'm not sure which mongo driver you are using, so this is more of a pseudo-code.
  db.users.findOne({email: user.email}, function(err, bson) {
    if (err) {
      return next(err);  // Express 3 will figure that error occured
                         // and will pass control to error handler:
                         // http://expressjs.com/guide.html#error-handling
    }

    // Could remember information from DB, but we'll just set `registred` field.
    user.registred = !!bson;
    next();
  });
}

确定注册状态后,您应该发送电子邮件或创建新用户,这将是请求处理程序(app.get的最后一个参数)。在执行处理程序之前,我们希望运行userInfo(只能传递userInfo(不是数组),但我个人更喜欢这种方式):

app.get('/register', [userInfo], function (req, res) {
  var user = req.session.user;
  var fn = user.registred ? sendActivationEmail
                          : registerUser;

  // If signatures differs or you need different reply for cases...
  // Well, you'll figure.
  fn(user.email, function (err) {
    return res.send(err ? 500 : 200);
  });
});

请求处理程序不应该被这两个函数内部发生的事情所困扰(有多少数据库查询,谁在发送电子邮件以及如何发送)。它只知道如果发生错误,提供的回调的第一个参数不为空。

现在关于创建新用户。编写registerUser函数,该函数将使用ID查询表,在DB中准备并保存用户信息。有关如何同步这两项操作,请参阅async和其他SO questions。创建完成或出错后,请使用结果调用callback。此功能应该是DB层的一部分。至少,创建db.js模块并导出重要内容,例如registerUser;通过电子邮件(在第一个中间件内部)查询用户信息也应该存在于DB层内。

类似适用于sendActivationEmail

旁注:您可以在{mongo documet中_id内放置您想要的任何内容,而不仅仅是ObjectId