Reputation: 14364
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);
Upvotes: 0
Views: 369
Reputation: 151112
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
.
Upvotes: 2