简体   繁体   中英

How to use async/await with Multiple Related Collections

I'm trying to wrap my head around using async/await with promises and mongoose references.

The code below almost works: the 3 posts get created and so do the comments for the first one, but none of the comments for the third post get created .

I'm assuming this is because I'm not putting the async/awaits in the right place, but I don't know how to organize the code so it works.

const seedData = [
  {
    title: 'Post 1',
    content: `beard sustainable Odd Future pour-over Pitchfork DIY fanny pack art party`,
    comments: [
      { content: 'comment1 1' },
      { content: 'comment1 2' },
      { content: 'comment1 3' }
    ]
  },
  {
    title: 'Post 2',
    content: `flannel gentrify organic deep v PBR chia Williamsburg ethical`,
    comments: []
  },
  {
    title: 'Post 3',
    content: `bag normcore meggings hoodie polaroid gastropub fashion`,
    comments: [{ content: 'comment3 1' }, { content: 'comment3 2' }]
  }
];

async function createPostsAndComments() {
  let promises = [];

  // delete existing documents
  Post.remove(() => {});
  Comment.remove(() => {});

  // create three posts along with their comments
  seedData.forEach(async postData => {
    // create the post
    let post = new Post({
      _id: new mongoose.Types.ObjectId(),
      title: postData.title,
      content: postData.content
    });

    // wait for the promise returned by `post.save`
    promises.push(
      post.save(error => {
        if (error) console.log(error.message);

        // create the comments of the current post
        postData.comments.forEach(async commentData => {
          const comment = new Comment({
            content: commentData.content,
            post: post._id
          });

          // wait for the promise from `comment.save`
          promises.push(
            comment.save(error => {
              if (error) console.log(error.message);
            })
          );
        });
      })
    );
  });
  return Promise.all(promises);
}

async function initSeed() {
  mongoose.connect(process.env.DATABASE, { useMongoClient: true });
  await createPostsAndComments();
  mongoose.connection.close();
}

initSeed();

In case it's useful, here are the schemas:

import mongoose from 'mongoose';

exports.commentSchema = new mongoose.Schema(
  {
    content: {
      type: String,
      required: true
    },
    post: { type: mongoose.Schema.Types.ObjectId, ref: 'Post' }
  },
  {
    toJSON: { virtuals: true }, // TODO: what's this?
    toObject: { virtuals: true }
  }
);
exports.Comment = mongoose.model('Comment', exports.commentSchema);

import mongoose from 'mongoose';

exports.postSchema = new mongoose.Schema({
  _id: mongoose.Schema.Types.ObjectId,
  title: {
    type: String,
    required: true
  },
  content: {
    type: String,
    required: true
  },
  comments: [{ type: mongoose.Schema.Types.ObjectId, ref: 'Comment' }]
});
exports.Post = mongoose.model('Post', exports.postSchema);

You need to actually await the async calls that return a promise. Like .remove() and .save() and basically every single interaction with the database. We can also make some things simpler with Promise.all() :

// <<-- You use async because there are "inner" awaits.
// Otherwise just return a promise
async function createPostsAndComments() {   
  let promises = [];

  // delete existing documents  <<-- This is actually async so "await"
  await Promise.all([Post,Comment].map(m => m.remove()));

  // create three posts along with their comments <<-- Not actually async
  seedData.forEach(postData => {
    // create the post
    let post = new Post({
      _id: new mongoose.Types.ObjectId(),
      title: postData.title,
      content: postData.content
    });

    // wait for the promise returned by `post.save`   <<-- You mixed a callback here
    promises.push(post.save());
    // create the comments of the current post  // <<-- Again, not async
    postData.comments.forEach(commentData => {
      const comment = new Comment({
        content: commentData.content,
        post: post._id
      });
      // <<-- Removing the callback again
      promises.push(comment.save())
    });
  });
  return Promise.all(promises);
}

// Just write a closure that wraps the program. async allows inner await
(async function()  {
  try {
    const conn = await mongoose.connect(process.env.DATABASE, { useMongoClient: true }); // <-- Yep it's a Promise
    await createPostsAndComments();
  } catch(e) {
    console.error(e);
  } finally {
    mongoose.disconnect();
  }
})();

Alternately just make the .remove() calls part of the promises array as well, and now there is not need for async/await within that function. It's just promises anyway:

function createPostsAndComments() {   
  let promises = [];

  //await Promise.all([Post,Comment].map(m => m.remove()));
  promises = [Post,Comment].map(m => m.remove());

  // create three posts along with their comments <<-- Not actually async
  seedData.forEach(postData => {
    // create the post
    let post = new Post({
      _id: new mongoose.Types.ObjectId(),
      title: postData.title,
      content: postData.content
    });

    // wait for the promise returned by `post.save`   <<-- You mixed a callback here
    promises.push(post.save());
    // create the comments of the current post  // <<-- Again, not async
    postData.comments.forEach(commentData => {
      const comment = new Comment({
        content: commentData.content,
        post: post._id
      });
      // <<-- Removing the callback again
      promises.push(comment.save())
    });
  });
  return Promise.all(promises);
}

Or even just await everything instead of feeding to Promise.all :

async function createPostsAndComments() {   

  await Promise.all([Post,Comment].map(m => m.remove()));

  for( let postData of seedData ) {
    // create the post
    let post = new Post({
      _id: new mongoose.Types.ObjectId(),
      title: postData.title,
      content: postData.content
    });

    await post.save());

    for ( let commentData of postData.comments ) {
      const comment = new Comment({
        content: commentData.content,
        post: post._id
      });

      await comment.save())
    }
  }

}

Those seem to be the concepts you are missing.

Basically async is the keyword that means the "inner block" is going to use await . If you don't use await then you don't need to mark the block as async .

Then of course any "Promise" needs an await in place of any .then() . And just don't mix callbacks with Promises. If you really need to, then you can wrap callback style returns in a Promise, but your code here does not need them.

The other main consideration is "error handling". So all that we need do using async/await keywords is implement try..catch as is shown. This is preferable to .catch() in Promise syntax, and essentially defers all errors thrown from the "inner" function to the "outer" block and reports the error there.

So there is no need to add error handling "inside" the createPostsAndComments() function, since that function itself is called within a try..catch .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM