ExpressJS Несколько промежуточных программ, подключенных к обратным вызовам

У меня есть приложение ExpressJS, которое принимает данные формы и выполняет следующие действия: 1. проверяет наличие всех необходимых значений, 2. проверяет правильность данных, 3. добавляет запись в базу данных для получения уникального идентификатора, 4. использует идентификатор и данные для вызова отдельного сервера, 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() в полученный обратный вызов, это единственный способ, которым я вижу, как он работает.

Спасибо, Аарон.


person Alpaus    schedule 02.04.2015    source источник
comment
Здравствуйте, будет здорово, если вы разместите здесь код   -  person Nicolai    schedule 02.04.2015


Ответы (3)


Довольно легко реализовать все ваши шаги в одном промежуточном программном обеспечении. Я включил некоторый псевдокод ниже (который делает различные предположения о том, как устроен ваш код, потому что вы не предоставили детали реализации, но это просто для того, чтобы дать представление).

Он использует пакет 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();
    });
  });
});
person robertklep    schedule 02.04.2015

Вы можете поместить все в один модуль и просто использовать обратные вызовы для прохождения каждого шага, но в этом случае вы можете получить "ад обратных вызовов".

Поэтому я могу предложить асинхронный пакет npm, который, на мой взгляд, лучше.

используя эту библиотеку, ваш код будет выглядеть так:

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);
}
person Nicolai    schedule 03.04.2015

Спасибо Николай и robertklep за ответы. Хотя я думаю, что оба ответа действительно отвечают на вопрос, я понял, когда сам работал над этим, что не смог увидеть лес за деревьями.

Я мог бы просто передать следующую функцию через каждую функцию обратного вызова, пока не достигну последней, и вызвать ее, чтобы передать управление обратно в стек промежуточного программного обеспечения. Это также позволяет мне просто вызывать next(err) внутри любой из этих функций.

Итак, мой ответ очень похож на концепцию, изложенную Николаем, за исключением того, что я не думаю, что мне нужно использовать асинхронный пакет в этом случае, потому что я не чувствую, что этот конкретный случай привел меня в ад обратного вызова.

Вот мой ответ на мой собственный вопрос:

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

и, в свою очередь, оставшиеся шаги вызываются последовательно после завершения каждого действия.

person Alpaus    schedule 03.04.2015