I have a back-end using firebase-admin and express to allow post requests from the client to the server to make changes to the firestore I have that contains stuff like user data (this is a test and not a real product). I want to check if a document already exists so a user cannot register with that username again. I have first seen instances of doc.exists but that returns undefined for me and I looked into the documentation and found doc.empty which is said to check if a document is empty. I tried it but it returned a promise rejection error. If I changed that line to .exists or to something else, that goes away so I have narrowed down the issue to that line.
index.js (backend)
app.post("/registeruser", function (req, res) {
res.setHeader("Content-Type", "application/json");
try {
const username = req.body.username;
const password = req.body.password;
const passwordEncrypted = HmacSHA1(password, JSON.parse(fs.readFileSync("./keys.json"))["passwordEncryptKey"]).toString();
// console.log(username, password, passwordEncrypted);
try {
firestore.collection("users").get(username).then(function (data) {
if (data.empty == false) {
throw [true, "Already registered user!"];
}
}).catch(function (error) {
throw [true, error];
});
if (username == "") {
firestore.collection("users").add({
username: v4(),
passwordhash: passwordEncrypted,
email: "example#gmail.com",
}).then(function () {
return res.status(200).send(JSON.stringify({
error: false,
message: "Successfully registered user!",
}))
}).catch(function (error) {
throw [true, error];
});
}
else {
firestore.collection("users").doc(username).set({
username: username,
passwordhash: passwordEncrypted,
email: "example#gmail.com",
}).then(function () {
return res.status(200).send(JSON.stringify({
error: false,
message: "Successfully registered user!",
}));
}).catch(function (error) {
throw [true, error];
});
}
}
catch (error) {
throw [true, error];
}
}
catch (error) {
console.log(error);
const [isError, errorMessage] = error;
return res.status(404).send(JSON.stringify({
error: isError,
message: errorMessage,
}));
}
});
Terminal Output
(node:29448) UnhandledPromiseRejectionWarning: [object Array]
(node:29448) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). To terminate the node process on unhandled promise rejection, use the CLI flag --unhandled-rejections=strict (see https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode). (rejection id: 1)
(node:29448) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
You have multiple concurrent promise chains, and some of those can fail independently. You need to consolidate all your logic into one promise chain.
return firestore.collection("users").get(username)
.then((data) => {
if (data.empty == false) {
throw [true, "Already registered user!"];
}
})
.then(() => {
if (username == '') {
return firestore.collection("users").add({/* Your data */});
}
return firestore.collection("users").doc(username).set({/* Your data */});
})
.then(() => {
return res.status(200);
})
.catch((err) => {
return res.status(500);
});
You can also try using async/await which will significantly simplify logic like this.
Related
I've rewritten the following function about 6 different times and am still getting a "Cannot set headers after they are sent to the client" error. I have found several posts on the topic of promises but still cant figure it out:
Error: Can't set headers after they are sent to the client
Cannot set headers after they are sent to the client
Error: Setting header after it is sent - Help me understand why?
The following function is for a forum and is triggered when a comment is submitted. It check to see that the forum post exists, than if a parent comment exists (in the case it is a subcomment). I am using firestore.
index.js
const functions = require('firebase-functions');
const app = require('express')();
const {postOneForumComment,
} = require('./handlers/forumPosts');
app.post('/forumPost/:forumPostId/:parentId/comment', FBAuth, postOneForumComment);
exports.api = functions.https.onRequest(app);
forumPosts.js
// submit a new comment
exports.postOneForumComment = (req, res) => {
if (req.body.body.trim() === '')
return res.status(400).json({ comment: 'Must not be empty' });
const newComment = {
body: req.body.body,
forumPostId: req.params.forumPostId,
parentId: req.params.parentId
};
db.doc(`/forumPosts/${req.params.forumPostId}`) //check to see if the post exists
.get()
.then((doc) => {
if (!doc.exists) {
return res.status(404).json({ error: 'Post not found' });
}
else if (req.params.forumPostId !== req.params.parentId) { //check to see if the comment is a subcomment
return db.doc(`/forumComments/${req.params.parentId}`) //check to see if the parent comment exists
.get();
}
return "TopLevelComment";
})
.then((data) => {
if (data === 'TopLevelComment' || data.exists) {
return db.collection('forumComments').add(newComment); //post the comment to the database
}
return res.status(500).json({ error: 'Comment not found' });
})
.then(() => {
res.json(newComment);
})
.catch((err) => {
console.log(err.message);
res.status(500).json({ error: 'somethign went wrong' });
});
};
ERROR:
(node:29820) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting
a promise which was not handled with .catch(). (rejection id: 1)
(node:29820) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with
a non-zero exit code.
There are two ways of using promises. Either you use the then/catch callbacks or you can use async/await to allow you to write them synchronously.
then/catch method
// Some code before promise
somePromise.then(() => {
// Some code after promise action is successful
}).catch(err => {
// Some code if promise action failed
})
// Some code after promise definition you think should run after the above code
// THIS IS WHAT IS HAPPENING WITH YOUR CODE
async/await method
// Some code before promise
await somePromise;
// Some code after promise action is successful
The latter approach was introduces to avoid the callback hell problem and it seems that's where your error is arising from.
When using callback callbacks you must make sure that nothing is defined after the promise definition else it will run before the promise resolves (Which is counter-intuitive since placing code B after code B should make A run before B)
Your error is because your callbacks are probably running AFTER the response has been sent and express does not allow you to send multiple responses for a request.
You should make sure that where ever res.send or res.json is being called exist within the callback.
This article should help you understand promises much better...
Hope this helps...
For anyone who stumbles upon this here is a working solution using Promise.all to make sure all promises are fulfilled before moving on. It is not the prettiest function and I plan on going back and turning it into an async/await ordeal per #kwame and #Ajay's recommendation... but for now it works.
// post a comment
// TODO: turn into async await function
exports.postOneForumComment = (req, res) => {
if (req.body.body.trim() === '') return res.status(400).json({ comment: 'Must not be empty' });
const newComment = {
body: req.body.body,
createdAt: new Date().toISOString(),
forumPostId: req.params.forumPostId,
parentId: req.params.parentId,
username: req.user.username,
userImage: req.user.imageUrl,
likeCount: 0
};
const parentPost =
db.doc(`/forumPosts/${req.params.forumPostId}`).get()
.then((doc) => {
if (!doc.exists) {
res.status(404).json({ error: 'Post not found' });
return false;
}
return true;
})
.catch((err) => {res.status(500).json({ error: 'something went wrong while checking the post' });});
const parentComment =
req.params.forumPostId === req.params.parentId ? true :
db.doc(`/forumComments/${req.params.parentId}`).get()
.then((doc) => {
if (!doc.exists) {
res.status(404).json({ error: 'Comment not found' });
return false;
}
if (doc.forumPostId !== req.params.forumPostId) {
res.status(404).json({ error: 'Comment is not affiliated with this post' });
return false;
}
return true;
})
.catch((err) => {res.status(500).json({ error: 'something went wrong while checking the comment' });});
Promise.all([parentPost, parentComment])
.then((values) => {
if (values[0] && values[1]) {
return db.collection('forumComments')
.add(newComment)
.then(() => {
res.json(newComment);
});
}
return console.log("there was an error");
})
.catch((err) => {
res.status(500).json({ error: 'somethign went wrong with the submission' });
});
};
I am using axios, express validator and bcryptjs for the function of letting a user change their password in their account area. The field I am checking against is the one where the user needs to type in their existing password. It then hashes their input and checks it against the database hashed password. When doing other validation I am able to get the error message response back via axios but for this will not:
body("currentPass")
.custom((value, { req }) => {
return User.findOne({ _id: req.user }).then(userDoc => {
bcrypt.compare(value, userDoc.password).then(domatch => {
if (!domatch) {
return Promise.reject("no match");
}
});
});
})
The error I get is:
UnhandledPromiseRejectionWarning: no match Unhandled promise
rejection. This error originated either by throwing inside of an async
function without a catch block, or by rejecting a promise which was
not handled with .catch(). (rejection id: 3)
If I add a catch block then when validation fails the catch block fires instead of the Promise.reject
body("currentPass")
.custom((value, { req }) => {
return User.findOne({ _id: req.user })
.then(userDoc => {
bcrypt.compare(value, userDoc.password)
.then(domatch => {
if (!domatch) {
return Promise.reject("no match");
}
})
.catch(err => {
console.log('catch block error');
})
});
})
When trying to authenticate users with Mongoose I get the following warning in my console:
(node:20114) UnhandledPromiseRejectionWarning: undefined
(node:20114) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:20114) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
Trying to trace the stack doesn't result in anything, all I get is undefined. Similar questions exist here on Stackoverflow, but they don't apply to my situation. Any idea what this could cause?
My route controller is calling the findByCredentials function inside the Mongoose model:
Controller
static login(req, res) {
User.findByCredentials(req.body.email, req.body.password)
.then(user => {
return user.generateAuthToken().then(token => {
res.header("x-auth", token).json(user);
});
})
.catch(error => {
res.status(400).json({ message: "Invalid credentials." });
});
}
Model
userSchema.statics.findByCredentials = function(email, password) {
return this.findOne({ email }).then(user => {
if (!user) {
Promise.reject();
}
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, res) => {
res ? resolve(user) : reject();
});
});
});
};
The error undefined is coming from your Promise.reject() you are not passing any error message to it, so you are literally throwing undefined.
It is not caught in the catch in login, because you are not returning it from your findByCredentials method.
Solution:
userSchema.statics.findByCredentials = function(email, password) {
return this.findOne({ email }).then(user => {
if (!user) {
return Promise.reject('User not available');
}
return new Promise((resolve, reject) => {
bcrypt.compare(password, user.password, (err, res) => {
res ? resolve(user) : reject();
});
});
});
};
when I running my project, I get the error:
(node:5795) UnhandledPromiseRejectionWarning: Error: reply interface called twice
at Object.exports.assert (/Users/labikemmy/Downloads/React-Native-FriendChat/api/node_modules/hoek/lib/index.js:736:11)
at Function.internals.response (/Users/labikemmy/Downloads/React-Native-FriendChat/api/node_modules/hapi/lib/reply.js:164:10)
at bound (domain.js:301:14)
at Function.runBound (domain.js:314:12)
at reply (/Users/labikemmy/Downloads/React-Native-FriendChat/api/node_modules/hapi/lib/reply.js:72:22)
at bound (domain.js:301:14)
at runBound (domain.js:314:12)
at result.then (/Users/labikemmy/Downloads/React-Native-FriendChat/api/node_modules/hapi/lib/handler.js:105:36)
at <anonymous>
at process._tickDomainCallback (internal/process/next_tick.js:228:7)
(node:5795) UnhandledPromiseRejectionWarning: Unhandled promise rejection. This error originated either by throwing inside of an async function without a catch block, or by rejecting a promise which was not handled with .catch(). (rejection id: 1)
(node:5795) [DEP0018] DeprecationWarning: Unhandled promise rejections are deprecated. In the future, promise rejections that are not handled will terminate the Node.js process with a non-zero exit code.
null
I don't know it is bug or my code error? I'm screen hapi.js issues, and someone said the error is bug, another said 'reply() times is limit in same request'? if it is limited, how to change the code at below?
```
export default async function (request, reply) {
if (request.auth.credentials.email !== request.payload.email) {
await User.findOne({ email: request.auth.credentials.email }).then(
(user) => {
if (user) {
User.findOne({ email: request.payload.email }).then(
(friend) => {
if (friend) {
const stringId = `${friend._id}`;
const friendExists = user.friends.filter(f => `${f}` === stringId).length > 0;
if (!friendExists) {
user.friends.push(friend);
user.save();
reply({ friend: { fullName: friend.fullName, _id: friend._id } });
} else {
reply(Boom.conflict('You have added already this friend'));
}
} else {
reply(Boom.notFound(`Friend ${request.payload.email} doesn't exist`));
}
},
);
} else {
reply(Boom.notFound('Cannot find user'));
}
},
);
} else {
reply(Boom.conflict('Cannot add yourself as a friend'));
}
}
Hapi#16.4.1
Do you have any other plugins or lifecycle hooks like onPreHandler or something? Maybe there is some point your code that throws this error because you (or your code somehow) are calling reply interface before your actual response.
Also, I refactored your code. You are already utilizing JavaScript async interface, so you don't need to put "then" calls to your promises.
Try this and watch that what will come out:
export default async function (request, reply) {
if (request.auth.credentials.email === request.payload.email) {
return reply(Boom.conflict('Cannot add yourself as a friend'))
}
// I belive this is mongoose model
const user = await User.findOne({email: request.auth.credentials.email}).exec();
if (!user) {
return reply(Boom.notFound('Cannot find user'));
}
const friend = await User.findOne({email: request.payload.email}).exec();
if (!friend) {
return reply(Boom.notFound(`Friend ${request.payload.email} doesn't exist`));
}
const stringId = `${friend._id}`;
const friendExists = user.friends.filter(f => `${f}` === stringId).length > 0;
if (!friendExists) {
// hmmm shouldn't it be friend._id? user.friends.push(friend._id.toString());
user.friends.push(friend);
// better use this statement
// ref: http://mongoosejs.com/docs/api.html#document_Document-markModified
user.markModified('friends');
await user.save();
return reply({friend: {fullName: friend.fullName, _id: friend._id}});
} else {
return reply(Boom.conflict('You have added already this friend'));
}
}
I get these cryptic lines here:
DEBUG: Mongoose connected (node:5983)
UnhandledPromiseRejectionWarning: Unhandled promise rejection
(rejection id: 1): TypeError: Cannot read property 'then' of undefined
(node:5983) [DEP0018] DeprecationWarning: Unhandled promise rejections
are deprecated. In the future, promise rejections that are not handled
will terminate the Node.js process with a non-zero exit code.
How can I get useful debugging information so I don't have to guess where the exact issue is?
I believe it is somewhere in this file:
const mongoose = require('mongoose');
const helper = require('../config/helper');
const schema = require('./Schemas')
mongoose.connect(helper.getMongoose()).then(
() => {
console.log('DEBUG: Mongoose connected')
mongooseConnected();
},
(err) => {
console.log('DEBUG: Mongoose did not connect')
}
);
function mongooseConnected () {
makeSchema( schema.User,
{ id_google: '1',
type: 'person',
timestamp: Date.now()
});
}
function makeSchema (Schema, dataObj) {
const Class = mongoose.model('Class', Schema);
const Instance = new Class(dataObj);
Instance.save((err, results)=>{
if (err) {
return console.error(err);
}
}).then(() => {
console.log('Saved Successfully')
});
}
In your case you are providing a callback to your save function, this way mongoose will not return a Promise:
Instance.save((err, results)=>{
if (err) {
return console.error(err);
}
console.log('Saved Successfully')
})
If you still want to use Promise then you don't have to pass a callback function:
Instance.save().then(() => {
console.log('Saved Successfully')
}).catch(err => {
return console.error(err);
});
In general an unhandled Promise rejection means that you're missing a catch method to deal with the error. Simply including .then() after returning a promise only deals with the code if it runs successfully, whereas including a .catch block will skip .then and only run .catch, with the error as a callback when an error occurs while executing the code which returns the promise.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/catch
myModel.save()
.then(() => {
console.log('Saved');
})
.catch(err => {
console.log(err);
}