ExpressJS 多个中间件连接到回调
ExpressJS Multiple middlewares connected to callbacks
我有一个 ExpressJS 应用程序,它采用表单数据并执行以下操作:
1. 检查是否提供了所有必需的值,
2.验证数据是否有效,
3.在数据库中添加一条记录,得到一个唯一的ID,
4.使用ID和数据调用单独的服务器,
5. 根据服务器的响应,使用响应的详细信息更新数据库记录。
我正在为数据库使用 mongoskin。
我的问题与我如何控制流量有关。本质上,我将上述每个步骤都编写为中间件函数,因为我需要在每次回调时调用 next() 成功(或 next(err) 错误)。
我好像写了太多的中间件,应该能够将这些步骤分组到包含多个 'sub-functions' 的更大的中间件集中,但我不确定如何在 Express 中执行此操作,因为我需要每次异步函数调用完成时调用 next()。有没有正确的方法来做到这一点,或者这种 'one middleware per step' 方法真的是 运行 的正确方法吗?
编辑:根据要求发布一些代码。为简洁起见,这是部分代码:
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() 在结果回调中,这是我能看到它工作的唯一方法。
谢谢
亚伦
在一个中间件中实现所有步骤相当容易。我在下面包含了一些伪代码(它对您的代码的结构做出了各种假设,因为您没有提供实现细节,但这只是为了提供一个想法)。
它使用 on-headers 包来 "catch" 响应。
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();
});
});
});
您可以将所有内容都放在一个模块中,只使用回调来处理每个步骤,但在这种情况下您可以获得 "callback hell".
所以我可以建议 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);
}
感谢 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() 并为下一步编写另一个中间件,我只需将 next 传递给下一个函数,直到需要它为止。
这是,我可以调用第一个函数作为我的中间件,就像这样:
router.post('/doTransaction',
validateFields,
function(req, res, next) { //...
然后,当每个动作完成时,依次调用剩余的步骤。
我有一个 ExpressJS 应用程序,它采用表单数据并执行以下操作: 1. 检查是否提供了所有必需的值, 2.验证数据是否有效, 3.在数据库中添加一条记录,得到一个唯一的ID, 4.使用ID和数据调用单独的服务器, 5. 根据服务器的响应,使用响应的详细信息更新数据库记录。
我正在为数据库使用 mongoskin。
我的问题与我如何控制流量有关。本质上,我将上述每个步骤都编写为中间件函数,因为我需要在每次回调时调用 next() 成功(或 next(err) 错误)。
我好像写了太多的中间件,应该能够将这些步骤分组到包含多个 'sub-functions' 的更大的中间件集中,但我不确定如何在 Express 中执行此操作,因为我需要每次异步函数调用完成时调用 next()。有没有正确的方法来做到这一点,或者这种 'one middleware per step' 方法真的是 运行 的正确方法吗?
编辑:根据要求发布一些代码。为简洁起见,这是部分代码:
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() 在结果回调中,这是我能看到它工作的唯一方法。
谢谢 亚伦
在一个中间件中实现所有步骤相当容易。我在下面包含了一些伪代码(它对您的代码的结构做出了各种假设,因为您没有提供实现细节,但这只是为了提供一个想法)。
它使用 on-headers 包来 "catch" 响应。
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();
});
});
});
您可以将所有内容都放在一个模块中,只使用回调来处理每个步骤,但在这种情况下您可以获得 "callback hell".
所以我可以建议 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);
}
感谢 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() 并为下一步编写另一个中间件,我只需将 next 传递给下一个函数,直到需要它为止。
这是,我可以调用第一个函数作为我的中间件,就像这样:
router.post('/doTransaction',
validateFields,
function(req, res, next) { //...
然后,当每个动作完成时,依次调用剩余的步骤。