const addUSDCurrencyToCreatorPage = async () => {
try {
const page = await Pages.find({ username });
console.log("page --", page);
page.currency = "USD";
await page.save();
} catch (error) {
console.log(error);
}
};
I have this script in my Nodejs application where the try to perform the above function i.e. to add currency to the document.
When I run this script I get this error.
page.save() is not a function
How do I resolve this?
The Page returned from the find() callback will be an array of mongoose documents, hence the error page.save() is not a function.
Since page is an array you can use map() function like this:
page.map(async (p) => {
p.currency = "USD";
await p.save();
});
OR
Either use the findOne() method which returns a single Mongoose document that has the save method or use findOneAndUpdate() for an atomic update. Read more here
Example with findOneAndUpdate():
const addUSDCurrencyToCreatorPage = async () => {
try {
const page = await Pages.findOneAndUpdate({username},{ 'currency', 'USD' } }, { new: true }).exec();
// If `new` isn't true, `findOneAndUpdate()` will return the document as it was before it was updated.
console.log("page --", page);
} catch (error) {
console.log(error);
}
};
Related
I am trying to update my post on my MongoDB database, but it shows: Cannot read properties of null (reading 'updateOne')
const router = require("express").Router();
const Post = require("../moduls/Post");
router.post("/", async (req, res) => {
const newpost = Post(req.body);
try {
const savedPost = await newpost.save();
res.status(200).json(savedPost);
} catch (error) {
res.status(500).json(error)
}
});
Here I try to write a code for updating my post. But it doesn't work.
//Update Post
router.put("/:id", async (req, res) => {
// try {
const post = await Post.findById(req.params.id);
if (post.userId === req.body.userId) {
await post.updateOne({ $set: req.body })
}
else {
res.status(403).json("You can't update it")
}
// } catch (error) {
// res.status(500).json("Internal Error")
// }
})
module.exports = router;
Based on your question, there are a few things that are wrong in your code:
Add always a check that the operation has succeeded before moving on.
Use Post instead of post to perform operations on.(Post Mongoose model instead of an instance of a Post)
In your case you can use findOneAndUpdate no need to find the corresponding Post first and then update after.
router.put("/:id", async (req, res) => {
try {
const postUpdated = await Post.findOneAndUpdate(
{
_id: mongoose.Types.ObjectId(req.params.id),
userId: mongoose.Types.ObjectId(req.body.userId) // assuming it is saved as a mongo id
},
req.body,
{ new: true }
);
if (!postUpdated) {
throw new Error('could not update Post');
}
res.json(postUpdated);
} catch (e) {
res.sendStatus(500);
}
});
As an addition:
Your commented error handling is actually needed, due to the fact that Express does not handle the returned promise for you.(This is what makes you get a UnhandledPromiseRejectionWarning)
Your code also does not provide any form of validation of the incoming request, you might want to consider checking first what data you are receiving from the client before inserting it into the database.
I am building tests for my node/express controller methods and using #shelf/jest-mongodb. I am creating a document first, and then when I try to find that I have to run find twice from model in order to get the results. It should get the results in the first find instead.
test.js
const { Subscription } = require('../src/models/subscription.schemaModel'); // model
const {
createSubscription,
} = require('../src/controllers/subscription.controller');
const subData = {...};
beforeAll(async () => {
await mongoose.connect(
process.env.MONGO_URL,
{ useNewUrlParser: true, useUnifiedTopology: true },
(err) => {
if (err) {
console.error(err);
process.exit(1);
}
}
);
});
afterAll(async () => {
await mongoose.connection.close();
});
describe('creates a subscription ', () => {
it('can be created correctly', async () => {
const sub = await createSubscription(subData);
await Subscription.find(); // if I comment out this line, I would get 0 results.
const subs = await Subscription.find();
expect(subs[0].items[0].sku).toBe(233234);
});
});
subscription.controller.js
const Mongoose = require('mongoose');
const { Subscription } = require('../models/subscription.schemaModel');
const isTestEnv = process.env.NODE_ENV === 'test';
module.exports.createSubscription = async (data) => {
try {
let error = null;
const doc = new Subscription(data);
doc.accountId = Mongoose.Types.ObjectId(doc.accountId);
await doc.save(function (err) {
if (err) {
logger.error(`createSubscription saving ${err}`);
error = err;
}
});
if (!error) {
logger.info(
`Subscription created => id: ${doc._id} store: ${doc.store}`
);
return doc;
} else {
return error;
}
} catch (err) {
logger.error(`createSubscription ${err}`);
}
};
The schemaModel file essentially contains the schema and exports model. Everything seems to work fine if I would do all the operations in the test file (schema+model+controller module)which defeats the purpose of testing my modules but not if I am importing. In this case I would have to run find() twice to get the results.
I have been trying multiple things from what I could find from googling, but no luck! Any help or lead would be appreciated. Also let me know if you need any other details.
Thank you!!
The only problem that posted code contains is that Mongoose promise API is mixed with legacy callback API. It appears that save results in race condition that is has been circumvented by random delay that extra find provides.
Although Mongoose documentation mentions that methods unconditionally return promises, a common pattern for JavaScript APIs that support both promises and callbacks is to enable promise control flow by omitting callback argument, and vice versa. This is most likely what happens here.
A way to avoid race conditions in such cases is to stick to promise control flow, e.g.:
beforeAll(async () => {
try {
await mongoose.connect(
process.env.MONGO_URL,
{ useNewUrlParser: true, useUnifiedTopology: true },
)
} catch (err) {
console.error(err);
process.exit(1);
}
});
I've a mongoose schema in which a virtual property is defined:
mortSchema.virtual('count').get(function () {
return Pickup.countDocuments({ mortician: this._id }).exec().then((count) => { console.log(count) })
})
I'm rendering it using express like this:
try {
const morticians = await Mortician.find({})
res.render('morticians', {
title: 'Mortician',
morticians: morticians
})
console.log(await morticians.count)
} catch (err) { /* err */ }
Since the promise it not solved at rendering time, it's outputting Promise { <pending> }, before the then() function to the console. How to solve the promise, before passing it to res.render? Where to insert the missing await?
Workaround: Removing the virtual property from the schema like this is working, but not answering my question:
try {
const morticians = await Mortician.find({})
for (mortician of morticians)
await Pickup.countDocuments({ mortician: mortician._id },
(err, count) => { mortician.count = count })
res.render('morticians', {
title: 'Mortician',
morticians: morticians
})
} catch (err) { /* err */ }
My react component componentWillMount() makes an axios call sending an array of objects. My node/express API gets the request. I want to map over the array sent, finding that users username with a mongoDB call to my User collection. I want to then create a new attribute in the object called username and set it to the result. I need to wait for my map function to finish before I sent my new mapped array back to the front end. I'm using async await and Promise.all(). My front end is receiving back an array of null objects.
I've tried just using regular promises, but had no luck there either. I understand the concept of async await by using the key term async on your method and basically waiting for whatever you use await on to move forward. Maybe I have that explanation wrong, just can't seem to figure it out. Quite new to async/await and promises.
exports.getAuthorUserNames = async (req, res) => {
if (req.body.data) {
let mappedArr = req.body.data.map(nade => {
User.findOne({ _id: nade.authorID }, function(err, result) {
if (err) {
res.sendStatus(500);
} else {
nade.username = result.username;
}
});
});
res.status(200).send(await Promise.all(mappedArr));
}
};
I except the result to return an array of objects with a new attribute called username with the username obtained from result.username(db call). I am receiving an array of nulls.
exports.getAuthorUserNames = async (req, res) => {
try{
if (req.body.data) {
const mappedArr = req.body.data.map(nade => User.findOne({ _id: nade.authorID }));
const results = await Promise.all(mappedArr);
return res.status(200).send(results);
}
} catch(e){
//handle exception here
}
};
exports.getAuthorUserNames = async (req, res) => {
if (req.body.data) {
let mappedArr = req.body.data.map(async nade => {
await User.findOne({ _id: nade.authorID }).then(result => {
nade.author = result.username;
});
return nade;
});
res.status(200).send(await Promise.all(mappedArr));
}
};
I have the following code:
"use strict";
const Raven = require("raven");
Raven.config(
"test"
).install();
module.exports = function(Reservation) {
function dateValidator(err) {
if (this.startDate >= this.endDate) {
err();
}
}
function sendEmail(campground) {
return new Promise((resolve, reject) => {
Reservation.app.models.Email.send(formEmailObject(campground),
function(
err,
mail
) {
if (err) {
console.log(err);
Raven.captureException(err);
reject(err);
} else {
console.log(mail);
console.log("email sent!");
resolve(mail);
}
});
});
}
function formEmailObject(campground) {
return {
to: "loopbackintern#yopmail.com",
from: "noreply#optis.be",
subject: "Thank you for your reservation at " + campground.name,
html:
"<p>We confirm your reservation for <strong>" +
campground.name +
"</strong></p>"
};
}
Reservation.validate("startDate", dateValidator, {
message: "endDate should be after startDate"
});
Reservation.observe("after save", async function(ctx, next) {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
next();
} catch (e) {
Raven.captureException(e);
next(e);
}
});
};
Sorry for the poor formatting. When the flow is done I get this error:
(node:3907) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): Error: Callback was already called.
I am calling the next() callback in two places, one in the try code and one in the catch code. I assume that when it all goes right, next callback is called only once, and the same when it goes wrong. But it seems that it is called twice and I don't know why.
I also tried to call next outside the try/catch code but it results in the same error. If I left only the next that is called inside the catch code it doesn't throw the error.
Any idea? Thanks!
if you are using async function you shouldn't explicitly call next, it gets automatically called.
check out this github issue for loopback async/await
so your hook can be like the following.
Reservation.observe("after save", async ctx => {
try {
const campground = await Reservation.app.models.Campground.findById(
ctx.instance.campgroundId
);
const mail = await sendEmail(campground);
} catch (e) {
Raven.captureException(e);
throw e;
}
});
NB: you don't need to wrap it in try catch unless you want to modify/work with the error.
You should declare your sendEmail method as async as it returns a promise.
async function sendEmail(campground) {
...
}
After reading this article, I created a await-handler.js file which include following code.
module.exports = (promise) =>
promise
.then(data => ({
ok: true,
data
}))
.catch(error =>
Promise.resolve({
ok: false,
error
})
);
Then in MyModel.js file, I created a async function to get a value from database as follow.
const awaitHandler = require("./../await-handler.js")
const getMaxNumber = async (MyModel) => {
let result = await awaitHandler(MyModel.find());
if (result.ok) {
if (result.data.length) {
return result.data.reduce((max, b) => Math.max(max, b.propertyName), result.data[0] && result.data[0].propertyName);
} else {
return 0;
}
} else {
return result.error;
}
}
As per #Mehari's answer, I've commented call to next() method as follow:-
module.exports = function(MyModel) {
MyModel.observe('before save', async(ctx, next) => {
const maxNumber = await getMaxNumber (MyModel);
if(ctx.instance) {
...
set the required property using ctx.instance.*
like createdAt, createdBy properties
...
// return next();
} else {
...
code for patch
...
// return next();
}
})
}
This solves the warning issue whenever saving endpoint is triggered.
But the warning issue still appear when I run the endpoint to load the resource.Like
http://localhost:3000/api/MyModel
Previously, the issue appear only when the before save operation hook gets triggered.
After encountering this issue, I checked adding access and loaded operation hooks and I found that the the warnings are issued after loaded operation hook.
MyModel.observe('access', (ctx, next) => {
return next();
})
MyModel.observe('loaded', (ctx, next) => {
return next();
})
What could have caused this issue and how can it gets resolved?