在接受服务器请求之前等待种子数据

时间:2017-08-29 01:29:55

标签: javascript node.js mongodb express mongoose

正如标题所示,这里的问题是在加载基根之后运行的server.js中的一个函数。您可以在下面看到函数调用和根。

seedDB();
app.get("/",function(req,res)
{
    examBoard.find({}, function (err, examBoard)
    {
        console.log("examBoard.length: " + examBoard.length);
        res.render("landing", { examBoard: examBoard });
    });
});

该函数执行数据库的基本种子,因此必须在基本根之前运行。它会输出您在下图中看到的内容(大部分输出都被截断)。

enter image description here

红色框中的输出是基础根中console.log的结果。这是app.listen,位于代码的最底部,其上方包含所有内容。

app.listen(process.env.PORT, process.env.IP,function()
{
    console.log("Server started");
});

以下是seedDB的代码,其中包含完整代码,包括此hastebin链接中的数组(https://hastebin.com/acecofoqap.lua)(认为包含它们有点过分,因为它们相当大):

function seedDB() {
    user.remove({}, function (err) {
        if (err) {
            console.log("Could not remove user\n" + err);
        }
        else {
            console.log("Removed old user");
            examBoard.remove({}, function (err) {
                if (err) {
                    console.log("Could not remove examboards\n" + err);
                }
                else {
                    console.log("Removed old examboards");
                    question.remove({}, function (err) {
                        if (err) {
                            console.log("Could not remove questions\n" + err);
                        }
                        else {
                            console.log("Removed old questions");
                            user.register(new user
                                ({
                                    username: "admin",
                                    email: "jonathanwoollettlight@gmail.com",
                                    role: "admin"
                                }),
                                "lu134r7n75q5psbzwgch", function (err, user) {
                                    if (err) {
                                        console.log("Failed to add admin\n" + err);
                                    }
                                    else {
                                        console.log("Admin added");
                                        examboardData.forEach(function (examSeed) {
                                            examBoard.create(examSeed, function (err, exam) {
                                                console.log("Creating new examboard");
                                                if (err) {
                                                    console.log("Could not create new examboard\n" + err);
                                                }
                                                else {
                                                    console.log("Created examboard");
                                                }
                                            });
                                        });
                                        var topicIncrementor = 0;
                                        questionData.forEach(function (questionSeed) {
                                            question.create(questionSeed, function (err, question) {
                                                if (err) {
                                                    console.log("Could not create new question\n" + err);
                                                }
                                                else {
                                                    console.log("Created question");
                                                    examBoard.find({}, function (err, exams) {
                                                        for (var i = 0; i < exams.length; i++) {
                                                            for (var t = 0; t < exams[i].modules.length; t++) {
                                                                for (var q = math.floor(topicIncrementor / 12); q < exams[i].modules[t].topics.length; q++) {
                                                                    exams[i].modules[t].topics[q].questions.push(question);
                                                                    topicIncrementor++;
                                                                }
                                                                topicIncrementor = 0;
                                                            }
                                                            exams[i].save();
                                                        }
                                                    });
                                                }
                                            });
                                        });
                                    }
                                });
                        }
                    });
                }
            });
        }
    });
}
module.exports = seedDB;

为了让我的程序在这里工作,seedDB函数必须在基本根之前运行,如果你能提供解决方案或只是指出我正确的方向,那将非常感激。

1 个答案:

答案 0 :(得分:1)

底线是你的seedDB()需要异步回调或Promise解析本身,然后在该操作完成时甚至只启动服务器的'http'部分。原因是服务器在确认要加载数据之前甚至不接受请求。

实施种子功能

使用nodejs的现代版本,最简单的实现方法是使用async/await语法

async function seedDB() {

  // Remove existing data
  await Promise.all(
    [user,examBoard,question].map( m => m.remove({}) )
  );

  // Create the user, wrap callback method with Promise
  await new Promise((resolve,reject) => {
    user.register( new user({
      username: "admin",
      email: "jonathanwoollettlight@gmail.com",
      role: "admin"
    }),"lu134r7n75q5psbzwgch", (err, user) => {
      if (err) reject(err);
      resolve(user);
    });
  });

  // Create examBoard. .create() does actually accept an array.
  // Keep the results as well
  var exams = await examboard.create(examboadData);

  // Create questions. Same thing
  var questions = question.create(questionData);

  // Add questions to each exam topic

  for ( let question of questions ) { 
    for ( var i = 0; i < exams.length; i++ ) {
      for ( var t = 0; t < exams[i].modules.length; t++ ) {
        for ( var q = 0; q < exams[i].modules[t].topics.length; q++ ) {
          exams[i].modules[t].topics[q].questions.push(question);
        }
      }
      await exams[i].save();
    }
  }

}

稍微回过头来看看普通Promise实现的外观:

function seedDB() {

  // Remove existing data
  return Promise.all(
    [user,examBoard,question].map( m => m.remove({}) )
  )
  .then( () =>
    // Create the user, wrap callback method with Promise
    new Promise((resolve,reject) => {
      user.register( new user({
        username: "admin",
        email: "jonathanwoollettlight@gmail.com",
        role: "admin"
      }),"lu134r7n75q5psbzwgch", (err, user) => {
        if (err) reject(err);
        resolve(user);
      });
    })
  )
  .then( () => 
    Promise.all(
      // Create examBoard. .create() does actually accept an array.
      // Keep the results as well
      examboard.create(examboadData),

      // Create questions. Same thing
      question.create(questionData)
    )
  )
  .then( ([exams, questions]) => {
    // Add questions to each exam topic
    var items = [];

    for ( let question of questions ) { 
      for ( var i = 0; i < exams.length; i++ ) {
        for ( var t = 0; t < exams[i].modules.length; t++ ) {
          for ( var q = 0; q < exams[i].modules[t].topics.length; q++ ) {
            exams[i].modules[t].topics[q].questions.push(question);
          }
        }
        items.push(exams[i].save().exec());
      }
    }

    return Promise.all(items).then( () => Promise.resolve() );
  });

}

所以它基本上是相同的,除了我们“明显地链接”承诺而不是使用可以在现代环境中使用的await糖。

这里要理解的关键是“mongoose API中的所有东西都返回一个Promise”,因此有更简洁的方法来实现等待完成和链接调用。

这适用于.remove()来电的简单链接:

  await Promise.all(
    [user,examBoard,question].map( m => m.remove({}) )
  );

通过“循环所有已注册的模型”来“播种”初始数据时,我通常会写一些内容:

  await Promise.all(
    Object.keys(conn.models).map( m => conn.models[m].remove({}) )
  )

或者如果有些方法实际上没有承诺,那么你总是可以在Promise中“包装它们”,就像user.register假设它是“仅回调”一样。实际实现可能有所不同,但这是通过演示包装回调的常用方法。

在一天结束时,一切都正常等待,整个功能只有在一切都完成后才能解决。这使您可以转到重要部分。

实施等待服务器启动

使种子函数成为“异步”返回的全部原因是我们知道它何时完成,然后只有在确认后才调用.listen()方法,因此服务器根本不接受请求数据准备就绪。

同样,根据可用的节点版本,您可以使用async/await语法或链接承诺来解析。

因此,使用async/await,您的主服务器启动应该如下所示:

const mongoose = require('mongoose'),
      app = require('express')();
      // and other module includes, including the seedDB source

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

mongoose.Promise = global.Promise;
mongoose.set('debug',true); // useful before deployment to see requests

// Possibly other express handler set up

// Root handler
app.get('/', function(req,res) {
  examBoard.find({}, function (err, examBoard) {
    console.log("examBoard.length: " + examBoard.length);
    res.render("landing", { examBoard: examBoard });
  });
});

// Main startup
(async function() {
  try {

    const conn = await mongoose.connect(uri,options);

    // Seed and await
    await seedDB();

    // Then we listen
    await app.listen(5858);

    console.log('server listening');
  } catch(e) {
    console.error(e);
    mongoose.disconnect();  // Probably not realistically
  }
})();

此处async/await suguar允许您按顺序列出每个操作,也可以在try/catch块中列出任何错误,而不会导致所有其他回调混乱。

或者,“链接”方法是:

const mongoose = require('mongoose'),
      app = require('express')();
      // and other module includes, including the seedDB source

const uri = 'mongodb://localhost/test',
      options = { useMongoClient: true };

mongoose.Promise = global.Promise;
mongoose.set('debug',true); // useful before deployment to see requests

// Possibly other express handler set up

// Root handler
app.get('/', function(req,res) {
  examBoard.find({}, function (err, examBoard) {
    console.log("examBoard.length: " + examBoard.length);
    res.render("landing", { examBoard: examBoard });
  });
});

// Main startup
mongoose.connect(uri,options)
  .then( () => seedDB() )
  .then( () => app.listen(5858) )
  .then( () => console.log('server listening') )
  .catch( e => console.error(e) );

除了“链接”之外,我们在链的末尾使用.catch(),这真的唯一不同。

请注意,在所有情况下,此处的promise都是“fail-fast”,因为任何错误基本上都会分别被try/catch.catch()捕获,而不会尝试继续。在大多数情况下,这就是你想要的,但你可以通过在更细粒度的区域使用类似的块或.catch()处理程序来交替处理它。

设计说明

所以提出的问题有一些非常混乱的代码,有很多回调嵌套,这里展示的更现代的功能意味着“清理”并使事物再次具有功能性和可读性。你应该立即注意到理解上的显着差异。

我认为,仍有问题。主要的例子是“所有问题都被添加到所有内容中”,这可能适用于测试目的,但我怀疑这是你的意图。并且可能有更多有效的方法来加入这些问题,但它基本上不是问题的主题,只是“等待异步完成”。

因此,代码的某些部分会有一些变化,但仅限于实现的代码实际上没有做任何事情的地方,或者至少没有做任何您可能期望的事情。 (topicIncrementor只会暴露于循环初始化为0,并且永远不会有所不同)。

总的来说,这应该为你提供一个合理的指导,让你理解如何以不同的方式完成工作,以及看看这些任务是多么干净和简单,这样他们就可以完全按照他们的设计来阅读,而不是寻找更像是“功能性面条”。