async.waterfall不能使用mongoose异步函数

时间:2017-07-14 02:49:06

标签: node.js mongodb mongoose async.js

import Student from './api/user/student.model';
import Class from './api/user/class.model';
import User from './api/user/user.model';
import request from 'request';
import async from 'async';

const EventEmitter = require('events');

class MyEmitter extends EventEmitter {}

const myEmitter = new MyEmitter;


function seedDb() {
    Student.remove({}, (err, data) => {
        Class.remove({}, (err, data1) => {
            User.remove({}, (err, data2) => {
                User.create({
                    email: 'admin@example.com',
                    password: 'admin'
                }, (err, data3) => {
                    User.findOne({
                        email: 'admin@example.com'
                    }, (err, foundUser) => {
                        foundUser.save((err, done) => {
                            let url = 'https://gist.githubusercontent.com/relentless-coder/b7d74a9726bff8b281ace936757953e5/raw/6af59b527e07ad3a589625143fb314bad21d4f8e/dummydata.json';
                            let options = {
                                url: url,
                                json: true
                            }
                            request(options, (err, res, body) => {
                                if (!err && body) {
                                    let classId;
                                    async.forEachOf(body, (el, i) => {
                                        console.log(i);
                                        if (i % 4 === 0) {
                                            async.waterfall([(fn) => {
                                                Student.create(el, (err, success) => {
                                                    fn(null, success._id);
                                                })
                                            }, (id, fn) => {
                                                console.log('The class creation function is called');
                                                Class.create({name: `Class ${i}`}, (err, newClass)=>{
                                                    classId = newClass._id;
                                                    newClass.students.push(id);
                                                    newClass.save()
                                                    fn(null, 'done');
                                                })
                                            }])
                                        } else {
                                            async.waterfall([(fn) => {
                                                Student.create(el, (err, success) => {
                                                    fn(null, success._id)
                                                })
                                            }, (id, fn) => {
                                                console.log('Class find function is called and classId', id, classId)
                                                Class.findById(classId, (err, foundClass) => {
                                                    console.log(foundClass)
                                                    foundClass.students.push(id);
                                                    foundClass.save();
                                                    fn(null, 'done');
                                                })

                                            }])
                                        }
                                    })

                                }
                            });
                        })

                    });
                })
            })

        })


    })

}

我想做的是,我有20名学生的样本数据。我必须将这20名学生分布在5个班级中,每个班级包含4名学生。

阻止我实现这一点的是,对于某些迭代,classId的值为undefined

任何人都可以帮我吗?

2 个答案:

答案 0 :(得分:3)

Modern async / await Version

在没有看到实际架构和预期结果的情况下,你并不完全清楚你所追求的是什么,但我可以接近传真,希望你可以关注和学习。

正如评论中所指出的,在你目前正在做的事情中,有几个错误的东西以及过时的概念。但是作为解析输入url内容并将其播种到数据库的一般目标,我建议采用这样的方法:

const request = require('request-promise-native'),
      mongoose = require('mongoose'),
      Schema = mongoose.Schema;

mongoose.Promise = global.Promise;
mongoose.set('debug',true);

const input = 'https://gist.githubusercontent.com/relentless-coder/b7d74a9726bff8b281ace936757953e5/raw/6af59b527e07ad3a589625143fb314bad21d4f8e/dummydata.json';

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

const studentSchema = new Schema({
  "student_id": { type: Number, unique: true },
  "student_name": String,
  "student_email": String,
  "neuroticism": Number,
  "extraversion": Number,
  "openness_to_experience": Number
});

const classSchema = new Schema({
  "course_number": { type: Number, unique: true },
  "course_name": String,
  "teacher_name": String,
  "teacher_number": Number,
  "students": [{ type: Schema.Types.ObjectId, ref: 'Student' }]
});


const Student = mongoose.model('Student', studentSchema);
const Class = mongoose.model('Class', classSchema);

function log(data) {
  console.log(JSON.stringify(data,undefined,2))
}

function extractPaths(model) {
  return Object.keys(model.schema.paths).filter( p =>
    ['_id','__v'].indexOf(p) === -1
  );
}

(async function() {

  try {
    console.log('starting');
    const conn = await mongoose.connect(uri,options);
    console.log('connected');

    // Clean models
    await Promise.all(
      Object.keys(conn.models) // <-- is the same thing
      //['Student','Class','User']
        .map( m => conn.models[m].remove({}) )
    );
    console.log('cleaned');

    let response = await request({ uri: input, json: true });

    for (let res of response) {
      let student = extractPaths(Student)
        .reduce((acc,curr) => Object.assign(acc,{ [curr]: res[curr] }),{});
      log(student);

      let sclass = extractPaths(Class).filter(p => p !== 'students')
        .reduce((acc,curr) => Object.assign(acc,{ [curr]: res[curr] }),{});
      log(sclass);

      let fstudent = await Student.findOneAndUpdate(
        { student_id: student.student_id },
        student,
        { new: true, upsert: true }
      );

      let fclass = await Class.findOneAndUpdate(
        { course_number: sclass.course_number },
        {
          $setOnInsert: sclass,
          $addToSet: { 'students': fstudent._id }
        },
        { new: true, upsert: true }
      );

    }

  } catch(e) {
    console.error(e)
  } finally {
    mongoose.disconnect()
  }

})();

这里有一些截然不同的概念需要注意,主要是使用async/await语法,因为我们可以在现代nodejs版本中使代码更清晰。

与您现有代码的第一个重大不同之处在于处理清除数据的.remove()语句。因此,现在我们只需让Promise.all告诉我们所有操作何时完成,而不是链接回调:

    // Clean models
    await Promise.all(
      Object.keys(conn.models) // <-- is the same thing
      //['Student','Class','User']
        .map( m => conn.models[m].remove({}) )
    );

下一个重大转变是使用常规for of循环,因为我们将再次await来自其中包含的任何异步操作的响应。我们可能会有点发烧友并做一个类似的Promise.all来并行运行多次迭代,但这将作为一个例子。

因为我们await然后我们分别进行实际写入数据库的每次调用,我们可以使用响应数据来提供给下一个调用。因此,创建Student的响应可以稍后提交到Class

我们真正改变的另一件事是我们如何从Feed中提取数据来创建每个对象,以及我们如何实际进行更新。

而是使用.findOneAndUpdate()并发出"upserts"我们基本上&#34;寻找&#34;一个主键的当前文档,如果它不存在,我们&#34;创建&#34;一个新的,或在其他地方找到它然后我们只是&#34;更新&#34;有了新的信息。

这主要通过Class模型得到证明,我们在"students"数组上$addToSet,其中提供的学生_id值为&#34;更新&#34; ,它实际上不会创建同一个学生两次。两者都是因为Student在根据自己的student_id值进行处理时不会重复,$addToSet也不允许将Student的引用多次插入到Class中带有"students"数组的courses对象。

{ "_id" : ObjectId("5968597490aa0ed4e5db1c92"), "course_number" : 101, "teacher_number" : 539224, "teacher_name" : "Merideth Merrill", "course_name" : "Physics 1", "__v" : 0, "students" : [ ObjectId("5968597490aa0ed4e5db1c90"), ObjectId("5968597490aa0ed4e5db1c94"), ObjectId("5968597490aa0ed4e5db1c97"), ObjectId("5968597490aa0ed4e5db1c9a"), ObjectId("5968597490aa0ed4e5db1c9d"), ObjectId("5968597490aa0ed4e5db1ca0"), ObjectId("5968597490aa0ed4e5db1ca3"), ObjectId("5968597490aa0ed4e5db1ca6"), ObjectId("5968597490aa0ed4e5db1ca9"), ObjectId("5968597490aa0ed4e5db1cac") ] } { "_id" : ObjectId("5968597490aa0ed4e5db1cb1"), "course_number" : 102, "teacher_number" : 539224, "teacher_name" : "Merideth Merrill", "course_name" : "AP Physics C", "__v" : 0, "students" : [ ObjectId("5968597490aa0ed4e5db1caf"), ObjectId("5968597490aa0ed4e5db1cb3"), ObjectId("5968597490aa0ed4e5db1cb6"), ObjectId("5968597490aa0ed4e5db1cb9"), ObjectId("5968597490aa0ed4e5db1cbc") ] } { "_id" : ObjectId("5968597590aa0ed4e5db1cc1"), "course_number" : 103, "teacher_number" : 731037, "teacher_name" : "Kelly Boyd", "course_name" : "English 11", "__v" : 0, "students" : [ ObjectId("5968597590aa0ed4e5db1cbf"), ObjectId("5968597590aa0ed4e5db1cc3"), ObjectId("5968597590aa0ed4e5db1cc6"), ObjectId("5968597590aa0ed4e5db1cc9"), ObjectId("5968597590aa0ed4e5db1ccc") ] } 集合的最终输出,其中每个参考学生都是:

Student

当然,源中的所有.findOneAndUpdate()条目都包含在内,并填充到自己的集合中。

所以我们通常会在这里对一些技术进行现代化改造,结果是更清晰的代码,这些代码很容易遵循逻辑,并且我们还会减少很多来回的&#34;通过简单地使用最有效的方法来实际编写和读取数据,与数据库进行通信。处理每个项目时const async = require('async'), request = require('request'), mongoose = require('mongoose'), Schema = mongoose.Schema; mongoose.Promise = global.Promise; mongoose.set('debug',true); const input = 'https://gist.githubusercontent.com/relentless-coder/b7d74a9726bff8b281ace936757953e5/raw/6af59b527e07ad3a589625143fb314bad21d4f8e/dummydata.json'; const uri = 'mongodb://localhost/school', options = { useMongoClient: true }; const studentSchema = new Schema({ "student_id": { type: Number, unique: true }, "student_name": String, "student_email": String, "neuroticism": Number, "extraversion": Number, "openness_to_experience": Number }); const classSchema = new Schema({ "course_number": { type: Number, unique: true }, "course_name": String, "teacher_name": String, "teacher_number": Number, "students": [{ type: Schema.Types.ObjectId, ref: 'Student' }] }); const Student = mongoose.model('Student', studentSchema); const Class = mongoose.model('Class', classSchema); function log(data) { console.log(JSON.stringify(data,undefined,2)) } function extractPaths(model) { return Object.keys(model.schema.paths).filter( p => ['_id','__v'].indexOf(p) === -1 ); } async.series( [ (callback) => mongoose.connect(uri,options,callback), // Clean data (callback) => async.each(mongoose.models,(model,callback) => model.remove({},callback),callback), (callback) => async.waterfall( [ (callback) => request({ uri: input, json: true }, (err,res) => callback(err,res)), (response,callback) => async.eachSeries(response.body,(res,callback) => async.waterfall( [ (callback) => { let student = extractPaths(Student) .reduce((acc,curr) => Object.assign(acc,{ [curr]: res[curr] }), {} ); log(student); Student.findOneAndUpdate( { student_id: student.student_id }, student, { new: true, upsert: true }, callback ); }, (student,callback) => { console.log(student); let sclass = extractPaths(Class) .filter(p => p !== 'students') .reduce((acc,curr) => Object.assign(acc,{ [curr]: res[curr] }), {} ); log(sclass); Class.findOneAndUpdate( { course_number: sclass.course_number }, { $setOnInsert: sclass, $addToSet: { 'students': student._id } }, { new: true, upsert: true }, callback ); } ], callback ), callback ) ], callback ) ], (err) => { if (err) throw err; mongoose.disconnect(); } ) 是哪个。

当然,它不是你想要实现的100%,但至少应该以你能够遵循和学习的方式更有效地展示它。

实际的async.js版本

如果经历了所有这些后仍然坚持使用async.js,则此列表会更正用法:

s = int

答案 1 :(得分:1)

  

我想做的是,我有20名学生的样本数据。我必须将这20名学生分布在5个班级中,每个班级包含4名学生。

正如尼尔所说,我们可以消除一些回调和循环。我们的想法是根据功能分离代码。以下是我用可用信息解决问题的方法。

'use strict';
let _ = require('lodash');
const BATCH_COUNT = 4;

function seedDb() {
  clearAll().then(() => {
    return Promisea.all([
      createSuperAdmin(),
      fetchStudentDetails()
    ]);
  }).then((result) => {
    let user = result[0];
    let body = result[1];
    return createStudents(body);
  }).then((users) => {
    let studentsBatch = groupByIds(_.map(users, '_id'), BATCH_COUNT);
    return addStudentsToClass(studentsBatch);
  }).then(() => {
    console.log('Success');
  }).catch((err) => {
    console.log('err', err.stack);
  });
}

function addStudentsToClass(batches) {
  let bulk = Class.collection.initializeOrderedBulkOp();
  for (let i = 0; i < _.size(batches); i++) {
    bulk.insert({
      name: `Class ${i}`,
      students: batches[i]
    });
  }
  return bulk.execute();
}

function createStudents(users) {
  let bulk = Student.collection.initializeOrderedBulkOp();
  _.each(users, (user) => {
    bulk.insert(user);
  });
  return bulk.execute();
}

function createSuperAdmin() {
  return User.findOneAndUpdate({
    email: 'admin@example.com',
    password: 'admin'
  }, {}, {
    new: true,
    upsert: true
  });
}

function groupByIds(ids, count) {
  let batch = [];
  let index = 0;
  while (index < ids.length) {
    let endIndex = index + count;
    batch.push(ids.slice(index, endIndex));
    index = endIndex;
  }
  return batch;
}

function fetchStudentDetails() {
  let options = {
    url: 'https://data.json', // Your URL
    json: true
  };
  return new Promise((resolve, reject) => {
    request(options, (err, res, body) => {
      if (err) {
        return reject(err);
      }
      return resolve(body);
    });
  });
}

function clearAll() {
  return Promise.all([
    Student.remove({}).exec(),
    Class.remove({}).exec(),
    User.remove({}).exec()
  ]);
}