【问题标题】:Making mongoose.js queries run synchronously使 mongoose.js 查询同步运行
【发布时间】:2013-06-15 09:32:53
【问题描述】:

我有两个猫鼬系列。第一个存储地点列表,第二个是访问地点。我的节点代码通过并尝试获取每个地方的访问列表并构建一个我输出为 JSON 的字符串。第一个查询在第二个查询开始之前完成 - 有没有办法让它们同步运行?

【问题讨论】:

  • 如果订单很重要,为什么不在第一个查询完成后开始第二个查询(尽管我不明白为什么只要您的 JSON 构建代码等待它们都完成,订单就很重要?)跨度>

标签: node.js mongodb mongoose


【解决方案1】:

如果你使用的是 node.js,那么你应该使用https://github.com/caolan/async

当您必须从多个集合中获取数据时,您必须多次链接查询。

它会使您的代码复杂且难以阅读并且没有模块化。使用异步创建 使用 mongodb 和 node.js 的模块化

我的项目中的示例代码:

var async = require('async');

var createGlobalGroup = function(socket, data) {
    async.waterfall(
    [
    /**
     * this function is required to pass data recieved from client
     * @param  {Function} callback To pass data recieved from client
     */

    function(callback) {
        callback(null, socket, data);
    },
    /**
     * Step 1: Verify User
     */
    verifyUser,
    /**
     * Step 2: Check User Access Rights And Roles
     */
    checkUserAccessRightsAndRoles,
    /**
     * Step 3: Create Project
     */
    createNewGlobalGroup], function(err, result) {
        /**
         * function to be called when all functions in async array has been called
         */
        console.log('project created ....')
    });
}
verifyUser = function(socket, data, callback) {
//do your query
    /**
     * call next function in series
     * provide sufficient input to next function
     */
    callback(null, socket, data, {
        "isValidUser": true,
    });
}

checkUserAccessRightsAndRoles = function(socket, data, asyncObj, callback) {
    //do your query
    if(condition) {
        callback(null, socket, data, {
            roles: result,
            "isValidUser": asyncObj.isValidUser,
            "userId": asyncObj.userId,
        });
    } else {
    //no call back
    }
}

var createNewGlobalGroup = function(socket, data, asyncObj, callback) {
//wanna stop then no callback
}

【讨论】:

    【解决方案2】:

    没有用于 mongodb/mongoose 查询的本机同步 api(实际上您也不需要)。正如 WiredPrarie 所提到的,您应该链接查询,第二个在第一个完成后开始并运行回调。这是一个例子:

    function findVisits(placesQuery,callback){
        Places.find(placesQuery).exec(function(err,places){
            if (err || !places.length){
                console.log('there was a problem');
                callback(err, null);
            }else{
                var visitQuery = ... //however you want to filter places
                Visits.find(visitQuery).exec(function(err2,visits){
                    if (err2 || !visits.length){
                        console.log('there was a problem');
                        callback(err2,null);
                    }else{
                        callback(null, visits)
                    }
                });
            }
        });
    }
    

    【讨论】:

    • 修正你的答案:在 Node 中没有办法进行同步网络。
    • There is no native synchronous api for mongodb/mongoose queries 对我来说是个坏消息,我试图让自动增量像这里的 mongodb 示例一样工作:docs.mongodb.org/manual/tutorial/…getNextSequence 不能是异步的,因为它必须返回一个数字.尝试在猫鼬中应用该自动增量,但必须找到某种执行同步函数的方法:stackoverflow.com/questions/28357965/mongoose-auto-increment
    • 我不同意“(而且你不想要一个实用的)”评论。这是每个开发人员都有自己的需求。例如。在 Node REPL 控制台中进行调试,逐行分配结果会容易得多。我可能会去一个小插件来做到这一点。
    【解决方案3】:

    如果您使用的是 Node 8.x,您可以使用 async/await:https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/await

    await 运算符暂停异步函数的执行,直到 Promise 被解析并返回值。这样你的代码看起来会更加同步:

    const query1 = MyModel.find({ name: /john/i }, null, { skip: 10 });
    const result1 = await query1.exec();
    
    const query2 = MyModel.find({ name: /john/i }, null, { skip: 100 });
    const result2 = await query2.exec();
    

    查询将连续执行。

    【讨论】:

    • 感谢您将我指向“等待”——这似乎是真的:编写和读取 Mongoose/Mongo 查询要快得多。
    • 请记住,您仍然必须处理错误,它们将作为异常抛出。
    • 但它只在异步函数内部使用!
    【解决方案4】:

    旧解决方案:承诺

    如今,Mongoose 支持 Promise,因此您可以.then() 您的查询。例如:

    app.get('/notifications', function (req, res, next) {
      Users.findOne({
        username: req.body.username,
        password: req.body.password,
      }).then(user => {
        if (!user) {
          res.json({success: false, message: "Username or password incorrect."});
          return;
        }
    
        return Notifications.find({
          user: user._id
        }).then(notifications => {
          res.json({success: true, notifications});
        });
      ).catch(error => {
        // Standard way to handle errors in express
        next(error);
        // Or custom handling
        //console.error(error);
        //res.json({success: false, error: error.message});
      });
    });
    

    新的解决方案:异步等待

    既然 Javascript 有 async-await,你就可以使用它了,这样可以节省几行代码,而且代码会扁平一点:

    app.get('/notifications', async (req, res, next) => {
      try {
        const user = await Users.findOne({
          username: req.body.username,
          password: req.body.password,
        });
        if (!user) {
          res.json({success: false, message: "Username or password incorrect."});
          return;
        }
    
        const notifications = await Notifications.find({
          user: user._id
        });
        res.json({success: true, notifications});
      } catch (error) {
        next(error);
      }
    });
    

    我的首选解决方案:清理异步等待

    就我个人而言,我不喜欢在 express 回调函数中添加 async 关键字,因为这实际上不应该是一个异步函数:我不打算从中返回一个承诺。

    我更喜欢使用IIAFE:在同步代码和异步代码之间进行显式转换:

    app.get('/notifications', (req, res, next) => {
      (async () => {
        const user = await Users.findOne({
          username: req.body.username,
          password: req.body.password,
        });
        if (!user) {
          res.json({success: false, message: "Username or password incorrect."});
          return;
        }
    
        const notifications = await Notifications.find({
          user: user._id
        });
        res.json({success: true, notifications});
      })().catch(error => {
        next(error);
      });
      // Remember to use () to call the async function!
      // You can also shrink the above to simply .catch(next);
    });
    

    不要忘记发现错误!

    无论您使用哪种方法,如果 async 函数返回错误(以被拒绝的承诺的形式),并且 express 不处理该错误,这称为unhandled rejection,Node 可能会决定使您的进程崩溃!

    【讨论】:

      【解决方案5】:

      为了同步,我使用了 es6-promise。

      var Promise = require('es6-promise').Promise
        , mongoose = require('mongoose')
        , Schema = mongoose.Schema;
      
      // define schemas and models.
      var placeSchema = new Schema({
              name: { type: String },
              memo: { type: String }
          })
        , Places = mongoose.model('place', placeSchema)
        , visitSchema = new Schema({
              placeName: { type: String }, // foreign key for place.
              visitor: { type: String },
              comment: { type: String }
          })
        , Visits = mongoose.model('visit', visitSchema);
      
      // query for visits by visitor and place.
      function findVisitsWithPlace(visitor, place) {
          return new Promise(function (resolve, reject) {
              Visits.find({
                  visitor: visitor,
                  placeName: place.name
              }, function (error, visits) {
                  if (error) {
                      reject(error);
                      return;
                  }
      
                  // build a result object you want.
                  // ()
                  resolve({
                      place: place,
                      visits: visits
                  });
              });
          });
      }
      
      // functions for node route.
      module.exports = {
          // - access to "GET /placevisits/?visitor=Visitor-1".
          get: function (request, response) {
              var visitor = request.query.visitor;
      
              // - to get the places...
              Places.find({}, function (error, places) {
                  Promise.all(places.map(function (place) {
                      // - run the child queries with parent object...
                      return findVisitsWithPlace(visitor, place);
                  })).then(function (placeAndVisits) {
                      // - and get result.
                      // placeAndVisits have still contain visits empty.
                      // exclude them.
                      var result = [];
                      placeAndVisits.forEach(function (placeandvisit) {
                          if (placeandvisit.visits.length != 0) {
                              result.push(placeandvisit);
                          }
                      });
                      response.json(result);
                  });
              });
          }
      };
      

      我得到如下 JSON。

      [
          {
              "place": {
                  "_id": "564e58a1dbed862155771d46",
                  "name": "Place-A",
                  "memo": "A memo for Place A."
              },
              "visits": [
                  {
                      "_id": "564e58cedbed862155771d49",
                      "placeName": "Place-A",
                      "visitor": "Visitor-1",
                      "comment": "A comment for Place A by Visitor-1"
                  },
                  {
                      "_id": "564e58dcdbed862155771d4a",
                      "placeName": "Place-A",
                      "visitor": "Visitor-1",
                      "comment": "2nd visit. Again comment for Place A by Visitor-1"
                  }
              ]
          },
          {
              "place": {
                  "_id": "564e58afdbed862155771d47",
                  "name": "Place-B",
                  "memo": "A memo for Place B."
              },
              "visits": [
                  {
                      "_id": "564e58ebdbed862155771d4c",
                      "placeName": "Place-B",
                      "visitor": "Visitor-1",
                      "comment": "A comment for Place B by Visitor-1"
                  }
              ]
          }
      ]
      

      【讨论】:

        【解决方案6】:

        这是使用 MongooseJS 发出伪同步请求的另一种方法。这里的想法是创建一个需要执行的查询队列。然后创建一个递归调用的函数,直到队列耗尽。一旦队列用尽,递归停止并为原始请求发回响应。我正在使用 Express Routes,因此所有这些代码都封装在我的路由处理程序中。在本例中为 HTTP POST。

        var express = require('express');
        var router = express.Router();
        
        //POST /auth/create
        router.post('/create', function(req, res) {
            var queue = [
                {"schema": require('..\\models\\people.js'), "query": {username: req.body.username}},
                {"schema": require('..\\models\\members.js'), "query": {username: req.body.username}}
            ],
            retData = []; 
        
            var curTask = 0.
        
        
            function recurse()
            {   
                if(curTask < queue.length){
                        var task = queue[curTask];
                        task.schema.findOne(task.query, function(err, data){
                        retData.push(err || data);
                        curTask++;
                        recurse();
                    })
                }else{
                    res.json(retData);
                }
        
            }
        
            recurse();
        
        });
        
        
        
        module.exports = router;
        

        【讨论】:

          【解决方案7】:

          这就是我今晚最终要做的事情。

          mongoose.createConnection("mongodb://localhost:27017/chemresearch")
          .then(async db => {
              const collection = db.collection("chemical");
              const all = collection.find({});
          
              while(all.hasNext()) {
                  let chemical = await all.next();
                  await work(chemical);
              }
          });
          

          work 方法只是返回一个承诺。

          const work = (chemical) => new Promise((resolve, reject) => {
              const casNumber = chemical.casRegistryNumber;
              analysis(casNumber, (error) => {
                  error ? reject(error) : resolve(casNumber);
              })
          });
          

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 2018-08-12
            • 2023-04-08
            • 1970-01-01
            • 2015-08-15
            • 1970-01-01
            • 2016-03-03
            • 2017-02-17
            • 2016-06-02
            相关资源
            最近更新 更多