ExpressJS多个中间件连接到回调

时间:2015-04-02 12:02:38

标签: node.js express next mongoskin

我有一个ExpressJS应用程序,它接受表单数据并执行以下操作: 1.检查提供的所有必需值, 2.验证数据是否有效, 3.向数据库添加记录以获取唯一ID, 4.使用ID和数据来调用单独的服务器, 5.在服务器响应后,使用响应的详细信息更新数据库记录。

我使用mongoskin作为数据库。

我的问题与我如何控制流量有关。基本上我已经将上述每个步骤作为中间件函数编写,因为我需要在每次回调时调用成功(或错误的下一个(错误))next()。

似乎我编写了太多的中间件,并且应该能够将这些步骤分组为包含多个子功能的更大的中间件集合'但我不确定如何在Express中执行此操作,因为每次异步函数调用完成时我都需要调用next()。是否有正确的方法来执行此操作,或者每个步骤是否有一个中间件'真的是正确的方法来运行它吗?

编辑:根据要求发布一些代码。为简洁起见,这是部分代码:

function validateFields(req, res, next) {
    //...
    //iterate over req.body to confirm all fields provided
    //...
    if (allDataProvided) {
        //...
        //iterate over req.body to confirm all fields valid
        //...
        if (allDataValid) {
            return(next());
        } else {
            return(next(err));
        }
    } else {
        return(next(err));
    }
},

//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(req, res, next) {
    counters.findAndModify(
      { _id: "receiptid" },
      [['_id','asc']],
      { $inc: { seq: 1 } },
      {},
      function(err, doc) {
           if (err) {
               return next(err);
            } else {
              req.receiptid = doc.seq;
              return next();
            }
        });
},

//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(req, res, next) {
    txns.insert(
        { _id : req.receiptid, 
          body : req.body,
          status : "pending"},
          {},
          function(err, r) {
            if (err) {
              return next(err);
            } else {
              return next();
            }
        });
},

//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(req, res, next) {
    remoteapi.processTransaction(
        { data: req.body,
          receiptid: req.receiptid },
          function(err, r) {
            if (err) {
                return next(err);
            } else {
                req.txnReceipt = r;
                return next();
            }
         });
},

//update the record in the database collection (txns) with the server response
function updateDatabase(req, res, next) {
    txns.updateById(req.receiptid, 
                    { $set :{status : "success",
                             receipt: req.txnReceipt }
                    }, function (err, r) {
                           if (err) {
                               return next(err);
                           } else {
                               return next();
                           }
                        });
    }

由于它目前支持上述功能,我使用此中间件的路线如下所示:

router.post('/doTransaction', 
        validateFields, 
        getNextID, 
        createTransaction, 
        processTransaction, 
        updateDatabase, 
        function(req, res, next) { //...

似乎我应该能够创建一个中间件函数,它连续完成所有这些事情,而不必每个都是一个单独的中间件,但由于每个中间件都有一个异步函数,我需要调用next( )在最终的回调中,这是我能看到它工作的唯一方法。

由于 亚伦

3 个答案:

答案 0 :(得分:1)

在一个中间件中实现所有步骤相当容易。我在下面添加了一些伪代码(对代码的结构做了各种假设,因为你没有提供实现细节,但只是提出一个想法)。

它使用on-headers包来“捕获”回复。

var onHeaders = require('on-headers')

// Your middleware function
app.use(function(req, res, next) {

  // Update the database when the response is being sent back.
  onHeaders(res, function() {
    // Do database update if we have a document id.
    if (req._newDocumentId) {
      db.collection.update(req._newDocumentId, data, function() {
        // can't do a lot here!
      });
    }
  });

  // Perform the requires steps
  if (! checkValuesAreSupplied(req)) {
    return next(new Error(...));
  }

  if (! validateValues(req)) {
    return next(new Error(...));
  }

  // Insert into database.
  db.collection.insert(data, function(err, doc) {
    if (err) return next(err);

    ...process the newly created doc...

    // Store _id in the request for later.
    req._newDocumentId = doc._id;

    // Make the call to the separate server
    makeCallToOtherServer(otherData, function(err, response) {
      if (err) return next(err);

      ...process response...

      return next();
    });
  });
});

答案 1 :(得分:1)

您可以将所有内容放在一个模块中,只需使用回调来完成每一步,但在这种情况下,您可以获得"回调地狱"

所以我可以建议我认为更好的async npm package

使用此库,您的代码将如下所示:

function allInOneMiddleware(req, res, next) {
    async.waterfall([
        function (callback) {
            validateFields(req, res, callback);
        },
        getNextID,
        createTransaction,
        processTransaction,
        updateDatabase
    ], function (err) {
        if (err) {
            return next(err);
        }
        // response?
    });
}

function validateFields(req, res, callback) {
    //...
    //iterate over req.body to confirm all fields provided
    //...
    if (allDataProvided) {
        //...
        //iterate over req.body to confirm all fields valid
        //...
        if (allDataValid) {
            return callback(null, req.body);
        }
        return callback(err);
    }
    return callback(err);
}

//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(body, callback) {
    counters.findAndModify(
        {_id: "receiptid"},
        [['_id', 'asc']],
        {$inc: {seq: 1}},
        {},
        function (err, doc) {
            if (err) {
                return callback(err);
            }
            callback(null, body, doc.seq);
        });
}

//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(body, receiptid, callback) {
    txns.insert(
        {
            _id: receiptid,
            body: body,
            status: "pending"
        },
        {},
        function (err, r) {
            if (err) {
                return callback(err);
            }
            callback(null, body, receiptid);
        });
}

//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(body, receiptid, callback) {
    remoteapi.processTransaction(
        {
            data: body,
            receiptid: receiptid
        },
        function (err, r) {
            if (err) {
                return callback(err);
            }
            callback(null, receiptid, r);
        });
}

//update the record in the database collection (txns) with the server response
function updateDatabase(receiptid, txnReceipt, callback) {
    txns.updateById(receiptid,
        {
            $set: {
                status: "success",
                receipt: txnReceipt
            }
        }, callback);
}

答案 2 :(得分:0)

感谢Nicolai和robertklep的回答。虽然我认为这两个答案都回答了这个问题,但我意识到自己正在努力解决这个问题,因为我没有看到森林中的树木。

我可以通过每个回调函数传递下一个函数,直到我到达最后一个并调用它将控制权传递回中间件堆栈。这也允许我简单地在任何这些函数中调用next(err)。

所以我的回答与Nicolai概述的概念非常相似,除非我不认为我需要在这种情况下使用异步包,因为我不觉得这个特殊情况让我回调地狱

以下是我对自己问题的回答:

function validateFields(req, res, next) {
    //...
    //iterate over req.body to confirm all fields provided
    //...
    if (allDataProvided) {
        //...
        //iterate over req.body to confirm all fields valid
        //...
        if (allDataValid) {
            getNextID(req, res, next)
        } else {
            return(next(err));
        }
    } else {
        return(next(err));
    }
},

//get an auto incrementing ID fields from a mongodb collection (counters)
function getNextID(req, res, next) {
    counters.findAndModify(
      { _id: "receiptid" },
      [['_id','asc']],
      { $inc: { seq: 1 } },
      {},
      function(err, doc) {
           if (err) {
               return next(err);
            } else {
              req.receiptid = doc.seq;
              createTransaction(req, res, next);
            }
        });
},

//insert a new record into the transaction collection (txns) using the new ID
function createTransaction(req, res, next) {
    txns.insert(
        { _id : req.receiptid, 
          body : req.body,
          status : "pending"},
          {},
          function(err, r) {
            if (err) {
              return next(err);
            } else {
              processTransaction(req, res, next);
            }
        });
},

//process the data on the remote web service using the provider's API (remoteapi)
function processTransaction(req, res, next) {
    remoteapi.processTransaction(
        { data: req.body,
          receiptid: req.receiptid },
          function(err, r) {
            if (err) {
                return next(err);
            } else {
                req.txnReceipt = r;
                updateDatabase(req, res, next);
            }
         });
},

//update the record in the database collection (txns) with the server response
function updateDatabase(req, res, next) {
    txns.updateById(req.receiptid, 
                { $set :{status : "success",
                         receipt: req.txnReceipt }
                }, function (err, r) {
                       if (err) {
                           return next(err);
                       } else {
                           return next();
                       }
                    });
}

因此,不是在成功完成每个异步函数并且必须为下一步编写另一个中间件时调用next(),而是直接将其传递给下一个函数,直到需要它为止。

这是,我可以将第一个函数称为我的中间件,如下所示:

router.post('/doTransaction', 
        validateFields, 
        function(req, res, next) { //...

反过来,当每个动作完成时,将依次调用其余步骤。