Getting Error: Can't set headers after they are sent - node.js

I am trying to implement search functionality in Node, mongoose.
There is two parameter I like to search upon, either by name or artist. If any of the two matches with the current database it should return value(making it restful)
However, it is sending response Error: Can't set headers after they are sent.
and Unhandled promise rejections are deprecated and even the response which i am getting is empty
I am trying to execute two queries in it, which i think might be the problem. How should i write it, or what is a correct way to write these type of functionality
Here is my current code
app.get('/search/:textValue', controller.findData)
and the findData
exports.findData = (req, res)=>{
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
return res.send(err)
})
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
return res.send(err)
})
return res.send(storeResult)
}
}
They are working for single queries perfectly fine, what changes should be made over here ?

The way you have it you're using res.send(storeResult) before you fill in storeResult. How so? You fill it in with your .then() callbacks, which haven't yet been invoked.
Try chaining your then callbacks.
Song.find({artists: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data);
})
.then(() => {
Song.find({name: new RegExp(searchParam, "i")}).lean()
.then((data)=>{
storeResult.push(data)
})
.then(() => {
console.log(storeResult)
res.send(storeResult)
})
})
.catch((err)=>{
console.log("Here is error")
console.log(err)
res.send(err)
})
}
Hint. Step-into in your debugger is useful for troubleshooting this kind of code.

Try this:
exports.findData = (req, res)=>{
let count=0;
const searchParam = req.params.textValue;
let storeResult = []
if(searchParam==null|| searchParam == undefined || searchParam==""){
return res.status(500).json("Send a valid input")
}
else{
Song.find({artists: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[0].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
if(count == 0) {
Song.find({name: new RegExp(searchParam, "i")}).lean().then((data)=>{
storeResult[1].push(data)
}).catch((err)=>{
count++;
return res.send(err)
})
}
if(count == 0) {
return res.send(storeResult)
}
}
}

Problem
You're starting with empty array let storeResult = []
Then you access its first element (which does not exist) storeResult[0].push(data)
This will trigger your catch callback. And then do a res.send(err)
Even if you called return it will still continue in (req, res) => {} . This is because the return is only for the (err) => { // } callback
Same thing with storeResult[1].push(data)
Finally you call return res.send(storeResult) which effectively finishes your (req, res) => {} callback and return another response to the client
Solution:
When you push to your storeResult array, omit the index. Like this
storeResult.push(data)
Note
Even when pushing correctly, an error might happen while accessing the database. This is why you also need to chain your callbacks like O. Jones answer says

Related

Struggling with calling a function that uses promises in node.js

I am struggling with some code... The 2 examples below I would think would work the same but the second example throws an error? I am also struggling to figure out the error, it's not bubbling up? Admittedly I am not a seasoned node developer so any guidance would be much appreciated! If it's relevant the create method in the module is calling the sequelize create.
This works
var p1 = deliverabiltyConfigs.create2(cfgObject);
return Promise.all([p1]).then(function([res1]) {
res.json({result: res1})
});
This does not
deliverabiltyConfigs.create2(cfgObject).then(res1 =>{
res.json({result: res1})
})
Here is the function that I am calling in a controller module
exports.create2 = (dConfig) => {
DeliverabilityConfig.create(dConfig)
.then(data => {
return data
})
.catch(err => {
return {
message:
err.message || "Some error occurred while createing this config."
};
});
};
The create2 function always returns null, so neither invocation will work. Promise.all([p1]) hides the problem, returning a promise to perform an array of no promises.
create2(cfgObject).then(res1 =>{ attempts to invoke then() on null, generating a more obvious error. But neither way works.
Fix by deciding which promise syntax you want, using each as follows:
Using original promise syntax....
exports.create2 = dConfig => {
// note the return
return DeliverabilityConfig.create(dConfig)
.catch(err => {
const message = err.message || "Some error occurred while createing this config.";
return { message };
});
};
// caller
deliverabiltyConfigs.create2(cfgObject).then(result =>{
res.json(result);
})
With recent syntactic sugar...
exports.create2 = async (dConfig) => {
try {
// its fine to not await here, since the caller will await
// but just to illustrate how you might perform more async work here...
return await DeliverabilityConfig.create(dConfig);
} catch (err) {
const message = err.message || "Some error occurred while createing this config."
return { message }
}
}
// caller
var result = await deliverabiltyConfigs.create2(cfgObject);
res.json(result);
Use Promise.all() to run >1 promise concurrently. You've only got one promise in the OP, so no reason for it here.

MongoDB How to catch errors in deleteMany

I have looked at MongoDB documentation for deleteMany() but it seems the only error it throws is WriteConcernError.
I am using Insomnia to make my requests.
Here is my request:
DELETE HTTP://localhost:5000/api/users/delete/usernames?usernames=["a","b","c"]
As you can see I have an array in query string
so I pass that to my function
# user.controller.js
function _deleteByUsernames(req, res, next) {
userService.deleteByUsernames(JSON.parse(req.query.usernames))
.then(() => res.status(200).json({"message": "User(s) successfully deleted!"}))
.catch(err => next(err));
}
# user.service.js
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
await User.deleteMany({username: {$in: usernames}});
} catch (err) {
throw err;
}
}
I know there no documents with usernames a, b and c
but deleteMany() doesn't return any error something like "Coulnd't find given parameter" etc.
because I don't want to response with "User(s) successfully deleted".
How can I catch that error if there is one.
Or How should I handle that?
You may change your functions to below,
# user.controller.js:
put async/await in function, and add code in try/catch block, and pass res as param in service function deleteByUsernames,
async function _deleteByUsernames(req, res, next) {
try {
await userService.deleteByUsernames(res, JSON.parse(req.query.usernames));
} catch (err) {
next(err);
}
}
# user.service.js:
deleteMany(), This function calls the MongoDB driver's Collection#deleteMany() function. The returned promise resolves to an object that contains 3 properties:
ok: 1 if no errors occurred
deletedCount: the number of documents deleted
n: the number of documents deleted. Equal to deletedCount.
async function _deleteByUsernames(res, usernames) {
let response = await User.deleteMany({ username: { $in: usernames } });
// ERROR
if (response.ok != 1) {
res.status(400).json({ "message": "User(s) not deleted, Something want wrong!" });
}
// SUCCESS
else {
res.status(200).json({
"message": `${response.deletedCount} User(s) successfully deleted out of ${response.n}"
});
}
}
Code is not tested, you can workaround and see what happens!
I think there is no error for no found parameters.
I don't know this is better than nothing for now.
I am not going to mark this as answered because I don't think this is the answer
async function _deleteByUsernames(usernames) {
return await User.deleteMany({username: {$in: usernames}})
.then(result => {
console.log(result);
return (result.deletedCount === 0 ?
"None of the selected user(s) deleted!":
(result.deletedCount !== usernames.length ?
`${result.deletedCount} out of ${usernames.length} selected user(s) successfully deleted!`:
"All selected user(s) successfully deleted!"))
})
.catch(err => {
return `Delete failed with error: ${err}`;
})
}
You can save your delete result in a variable and check for the error
async function _deleteByUsernames(usernames) {
try {
console.log(usernames);
let userDeleteResult = await User.deleteMany({username: {$in: usernames}});
if(!userDeleteResult ){
res.json({status: false, error: 'Some error'}) // or pass the error object here
}
} catch (err) {
throw err;
}
}

Mongoose document created. But change is not reflected immediately in the collection

favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, (req, res, next) => {
Favorites.findOne({ user: req.user._id })
.then(
(favoriteList) => {
//***SECTION A START//***
if (favoriteList == null) {
Favorites.create({
user: req.user._id,
})
.then(
(favoriteList) => {
console.log('promise resolved');
},
(err) => {
console.log('promise error');
next(err);
}
)
.catch((err) => next(err));
}
//***SECTION A END//***
Favorites.findOne({ user: req.user._id })
.then((favoriteList) => {
//***SECTION B START//***
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
favoriteList
.save()
.then(
(favoriteList_c) => {
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
},
(err) => {
next(err);
}
)
.catch((err) => {
next(err);
});
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
err.status = 404;
return next(err);
}
//***SECTION B END//***
})
.catch((err) => next(err));
},
(err) => next(err)
)
.catch((err) => next(err));
});
If a user post on /:dishId and the favourite document is not there for that user then a new document is created
in SECTION A (marked in code). The document is created fine as it prints promise resolved. But in SECTION B the else part is executed that means the newly created document is not found. But if the user tries again means in next go it can find that document and it gets updated in SECTION B IF block. Is there something I am missing. I am a beginner in nodejs, Please help!
You have to first understand the way NodeJs asynchronous code work. The default behavior of NodeJs is that whenever it finds an I/O operation, it delegates that opeation to either OS or worker thread based on OS capability in handling it and moves to the next line of code.
According to your code, the first Favorite.findOne() is called and while it is being executed by the engine, control jumps over to the next line of code, which is the second Favorite.findOne() and it tries to find the document. But At this point the document has not been created yet, So that's the reason when you run for the first time, it doesn't find the record, but for the second time onwards the document has been created using the Favorite.create() inside the first findOne's then().
So you need to re-factor your code by putting the second findOne() inside the firstOne. Well, you know what, you don't need to write the Favorite.findOne() two times. One findOne() is sufficient to accomplish your requirement.
Mongoose leverages promises, it means we can use async/await in the controller method.
favoriteRouter
.route('/:dishId')
.post(cors.corsWithOptions, authenticate.verifyUser, async (req, res) => {
try {
// find the FavoriteList
let favoriteList = await Favorites.find({
user: req.user._id
});
// using a library called lodash for simplicity
// you have to install it using npm i lodash and
// require at the top using let _ = require('lodash')
//If not found, create one and assign it to favorite list
if (_.isEmpty(favoriteList)) {
favoriteList = await Favorites.create({
user: req.user._id
});
}
// at this point, you must have a favoriteList either a found one or brand new
favoriteList.dishes.push(req.params.dishId)
let favoriteList_c = await favoriteList.save();
return res.json(favoriteList_c)
} catch (err) {
//handle error
res.status(501).send({message: 'unable to perform operation'});
}
});
I have added async to the controller callback function here and reomved the next parameter, as we don't need it. For reference visit this.
NOTE: the code I've wrtitten may not work if you simply copy/paste it in your program but the approach is fairly straight forward and you may need to do some tweakings based on Mongoose API documentation especailly for save() and create().
I am sure they will return the object after creating it.
Good Luck!!
There is no problem in your code but when the findOne of section A is getting executed at the same time section B code gets executed.
Using promise.then creates a complex code. Use async/await instead.
The following is just a small snippet from your code to use async/await
let favoriteList = await Favorites.findOne({ user: req.user._id });
if (favoriteList == null) {
await Favorites.create({
user: req.user._id,
})
}
favoriteList = await Favorites.findOne({ user: req.user._id })
if (favoriteList != null) {
favoriteList.dishes.push(req.params.dishId);
await favoriteList.save();
res.statusCode = 200;
res.setHeader('Content-Type', 'application/json');
res.json(favoriteList_c);
} else {
err = new Error(
'Something wrong with favorite list document of user ' +
req.user._id
);
}

Get async value from firestore

I am struggling with async operations. I am trying to simply get a value from firestore and storing it in a var.
I manage to receive the value, I can even save it in the var when I do that specifically (use the var within the get function) but I don't seem to manage the await properly when trying to save this in a flexible way:
async function getValues(collectionName, docName,) {
console.log("start")
var result;
var docRef = await db.collection(collectionName).doc(docName).get()
.then(//async// (tried this as well with async) function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return //await// (this as well with async) result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
console.log("end");
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
helpMessage = getValues('configuration','helpMessage');
Note: doc.data().text -> "text" is the name of the field where my value is stored in. Do I have to use .value here?
The result I get in the console is: info: Document data: { text: 'The correct text from the database' }
info: The correct text from the database
But using helpMessage in my code I get {}
Image from the Telegram bot where I am trying to use the helpMessage as a response to the '/help' command.
I have checked: getting value from cloud firestore,
Firebase Firestore get() async/await, get asynchronous value from firebase firestore reference and most importantly How do I return the response from an asynchronous call?. They either deal with multiple documents (using forEach), don't address the async nature of my problem or (last case), I simply fail to understand the nature of it.
Additionally, both nodejs and firestore seems to be developing rapidly and finding good, up-to-date documentation or examples is difficult. Any pointers are much appriciated.
You have things the wrong way around. It's much easier than you think it is.
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then(function (doc) {
if (doc.exists) return doc.data().text;
return Promise.reject("No such document");
}};
}
If a function returns a promise (like db.collection(...).doc(...).get()), return that promise. This is the "outer" return above.
In the promise handler (inside the .then() callback), return a value to indicate success, or a rejected promise to indicate an error. This is the "inner" return above. Instead of returning a rejected promise, you can also throw an error if you want to.
Now you have a promise-returning function. You can use it with .then() and .catch():
getValues('configuration','helpMessage')
.then(function (text) { console.log(text); })
.catch(function (err) { console.log("ERROR:" err); });
or await it inside an async function in a try/catch block, if you like that better:
async function doSomething() {
try {
let text = await getValues('configuration','helpMessage');
console.log(text);
} catch {
console.log("ERROR:" err);
}
}
If you want to use async/await with your getValues() function, you can:
async function getValues(collectionName, docName) {
let doc = await db.collection(collectionName).doc(docName).get();
if (doc.exists) return doc.data().text;
throw new Error("No such document");
}
Since getValues function returns a promise, you need to await getValues function while calling it.
Change getValues like so -
function getValues(collectionName, docName,) {
console.log("start")
var result;
return db.collection(collectionName).doc(docName).get()
.then(function (doc) {
if (doc.exists) {
console.log("Document data:", doc.data());
result = doc.data().text;
console.log(result);
return result;
} else {
// doc.data() will be undefined in this case
console.log("No such document!");
result = "No such document!";
return result;
}
}).catch (function (err) {
console.log('Error getting documents', err);
});
};
Then use getValues like so -
helpMessage = await getValues('configuration','helpMessage');
Explanation -
async, await are just syntactic sugar for Promises. async functions return a promise (or AsyncFunction more accurately) which needs to be resolved to use its enclosed value.
See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/async_function
Finally managed to get it working. Thanks for the input Tomalak!
getValues(help.collectionName, help.docName)
.then((text) => {
console.log(text);
help.message = text;
})
.catch((err) => { console.log("Error: ", err); });
function getValues(collectionName, docName) {
return db.collection(collectionName).doc(docName).get().then((doc) => {
if (doc.exists) {
return doc.data().text;
}
else {
return Promise.reject("No such document");
}});
}
bot.help((ctx) => ctx.reply(help.message));
Unfortunately, I can not pin-point the exact reason this worked. Some little fixes (missed comma in the console.log) and formatting definitely helped me understanding the structure though. Hope someone else finds this useful, when starting to play around with node and firebase.

Nodejs Async/Await in api controller responds with unknown error

export function getAllHost(req, res) {
function findAllHost() {
let query = {};
query.company = req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId;
query.active = true;
if(req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name);
query['name'] = {$regex: regexp, $options: 'i'};
}
return Host.find(query);
}
async function sendRes() {
let allHost = [];
let hosts = [];
try {
hosts = await findAllHost();
} catch(err){
console.log(err)
}
for (let host of hosts) {
allHost.push(new Host_V1(host));
}
return allHost
}
sendRes().then(data => res.status(200).json(data)).catch(error => {
console.log("error is", error);
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
});
});
}
I have been trying to adapt async/await into my code, so I converted one of the Promise based api controller to make use of async/await, but the thing that bothers me is that my server responds with 500, and the console.log inside my catch block doesn't print anything.
No error gets thrown.
I am using babel babel-plugin-syntax-async-functions to parse it.
What is it that I am doing wrong?
Your code is a bit overcomplicated, but judging by it you should receive an error in the console if one appears. It could instead be that you have a middleware producing an error? The main issue is that you're catching the error in the async function sendRes, so the .catch-method you call on the returned Promise will never be fired even if there is an error.
Like many others that are new to async/await, you've misunderstood and believe that you have to wrap every await expression in a try/catch-block. This is not the case. The error "trickles" up the call chain, and unless a particular function can provide a different return value, it's best to catch the error from the top-most callee. Take this simple example which shows a common anti-pattern: https://repl.it/repls/PunySafeInterfacestandard (await and async isn't even needed in these examples, but I added them for clarity)
But if you try to simplify your code, maybe something like the below snippet, you might be able to rule out if it's a middleware or not.
export async function getAllHosts (req, res) {
try {
let query = {
company: req.query && req.query.companyId ? req.query.companyId : res.locals.payload.companyId,
active: true
}
if (req.query && req.query.name) {
let regexp = new RegExp('\\b' + req.query.name)
query.name = {$regex: regexp, $options: 'i'}
}
let allHosts = (await Host.find(query)).map((host) => new Host_V1(host))
return res.json(allHosts)
} catch (e) {
console.log("error is", e)
res.status(error.status || 500).json({
message: error.status ? error.message : 'Server Error'
})
}
}
Take note of the (await Host.find(query)) line. If the Promise that's returned from Host.find() rejects, the .map()-method won't be executed on the data and execution will jump to the catch-block.
I also heavily discourage using Babel since async/await has been natively supported in Node since version 7.6.
EDIT : The value returned is indeed a promise
I think that the error is related to your 'SendRes.then' since what SendRes returns is not a promise but it's actually is the Array allHost
Your data argument is not an argument anymore it is the value returned from the sendRes function thanks to your async / await implementation.
const hosts = sendRes();
res.status(200).json(hosts);
if you want to handle an error you must return something from your catch block so that you can can handle it here.
if (!hosts) res.status(500);
To simplify your code you can also get rid of the sendRes function and make your getAllHost express middleware an async function
async getAllHost(req, res) { /*...*/ }
try {
let allHosts = await findAllHosts();
res.status(200).json(allHosts);
} catch (error) {
res.status(500).send({ error })
}

Resources