【问题标题】:ExpressJS Multiple middlewares connected to callbacksExpressJS 连接到回调的多个中间件
【发布时间】:2015-04-02 12:02:37
【问题描述】:

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

我正在使用 mongoskin 作为数据库。

我的问题与我如何控制流程有关。本质上,我将上述每个步骤都编写为中间件函数,因为我需要在每个回调中调用 next() 成功(或 next(err) 错误)。

似乎我编写了太多中间件,应该能够将这些步骤分组到包含多个“子功能”的更大的中间件集合中,但我不确定如何在 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( ) 在结果回调中,这是我可以看到它工作的唯一方法。

谢谢 亚伦

【问题讨论】:

  • 你好,如果你能在这里发布一些代码会很棒

标签: node.js express next mongoskin


【解决方案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();
    });
  });
});

【讨论】:

    【解决方案2】:

    您可以将所有内容放在一个模块中,然后使用回调来执行每个步骤,但在这种情况下,您可以获得“回调地狱”

    所以我可以建议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);
    }
    

    【讨论】:

      【解决方案3】:

      感谢 Nicolai 和 robertklep 的回答。虽然我认为这两个答案都回答了这个问题,但当我自己解决这个问题时,我意识到我没能只见树木不见森林。

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

      所以我的回答与 Nicolai 概述的概念非常相似,只是我认为在这种情况下我不需要使用 async 包,因为我不觉得这种特殊情况让我陷入回调地狱。

      这是我对自己问题的回答:

      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) { //...
      

      然后,当每个操作完成时,将依次调用其余步骤。

      【讨论】:

        猜你喜欢
        • 2018-01-20
        • 1970-01-01
        • 2021-09-26
        • 2012-09-26
        • 1970-01-01
        • 1970-01-01
        • 2021-04-27
        • 2016-06-01
        相关资源
        最近更新 更多