NodeJS UnhandledPromise warning when connecting to MongoDB - javascript

I am trying to connect to my MongoDB instance using nodejs. I expose the endpoint /mongo which is supposed to trigger the connection and creation of a document in the mongo db, as follows:
app.get('/mongo', (req, res) => {
try{
invoke();
} catch(err){
console.log(err);
}
res.send('all good.');
});
async function invoke() {
client.connect(err => {
const collection = client.db("CodigoInitiative").collection("Registered");
//create document to be inserted
const pizzaDocument = {
name: "Pizza",
shape: "round",
toppings: [ "Pepperoni", "mozzarella" ],
};
// perform actions on the collection object
const result = collection.insertOne(pizzaDocument);
console.log(result.insertedCount);
//close the database connection
client.close();
});
}
When I hit the endpoint though, it returns with the following error:
(node:15052) UnhandledPromiseRejectionWarning: MongoError: topology was destroyed. 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)
I'm confused because the method invocation was wrapped around a try/catch block, even though the error log claims it wasn't. Where did I go wrong here?

There could be connection error in your environment. And the error was a rejected promise, you cannot catch it via the try / catch block because the error was generated on asynchronous call stack.
an async function should alway return a promise:
async function invoke () {
return new Promise((resolve, reject) => {
client.connect(err => {
if (err) return reject(err)
...
})
})
}
the returned promise should be handled with .catch:
app.get('/mongo', (req, res) => {
invoke().then(() => res.send('all good'))
.catch(err => console.log('invoke error:', err))
})

Related

nodejs promises giving Unhandled Promise Rejection which seems to be handled properly

The problem is that i am getting UNhandledPromiseRejection error eveen though i think i have handled all the cases. The code flows from profileRoutes to Controller to Utils where the error comes first.
Inside the profileRoutes.js
router.get('/:username', async (r, s) => {
try{
let profileData = await getProfileData(r.params.username);
s.json({ success: true, payload: profileData });
}catch(err){
console.log('ending request processing by responding a error');
s.status(500).json({ success: false, message: 'err[0].message' });
}
});
Inside the controllers/index.js
const fetchQueue = [getUserRepos];
async function getProfileData(username) {
let profileData = {};
try{
let results = await Promise.all(fetchQueue.map(item => item(username)));
for (let i = 0; i < results.length; i++) {
profileData[getKeys[i]] = results[i];
}
return profileData;
}catch(err){
console.log('error log in controller/index getProfileData function');
throw err;
}
}
const getUserRepos = async (username) => {
try {
// const res = await utils.gqlSender(username, 'userRepos', { createdAt });
const res = await utils.gqlSender(username, 'userReposData');
return res.user.repositories;
} catch (err) {
console.log('error log in controller/index getUserRepos function');
throw err;
}
};
Inside the utils/index.js
const gqlSender = async (username, type, opt = {}) => {
axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if(res.data.errors) { // this is where the error is recieved and so i reject promise.
console.log('bef###re');
return Promise.reject (res.data.errors);
}
console.log('###',res.data);
return res.data;
}).catch(err => {
console.log('error in making axios request inside utils/index gqlSender function');
throw err;
// return Promise.reject(err);
});
The stack trace on making get request to /:username is-
error log in controller/index getUserRepos function
error log in controller/index getProfileData function
ending request processing by responding a error
bef###re
error in making axios request inside utils/index gqlSender function
(node:11260) UnhandledPromiseRejectionWarning: [object Array]
(node:11260) 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: 2)
(node:11260) [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.
I dont think i am missing any Promise Rejection.
Any help is appreciated. Thanks.
i have referred these answers previously -
What's the difference between returning value or Promise.resolve from then()
Do I need to return after early resolve/reject?
Your gqlSender function is not returning the promise that will get rejected, so it is not handled anywhere. You should write either
const gqlSender = (username, type, opt = {}) => {
return axios.post('', {
// ^^^^^^
query: gqlQuery(username, type, opt) // generates a needed graphQL query
}).then(res => {
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
});
};
or
const gqlSender = async (username, type, opt = {}) => {
// ^^^^^
const res = await axios.post('', {
query: gqlQuery(username, type, opt) // generates a needed graphQL query
});
if (res.data.errors) {
console.log('error in making axios request inside utils/index gqlSender function');
throw res.data.errors;
} else {
console.log('###',res.data);
return res.data;
}
}

Adding a catch block to a promise returns pending instead of rejected

I have a working apollo graphql express server. The only problem is express is complaining that I don't have a catch block on a promise I'm using to verify a jwt token:
(node:96074) 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()
I can add a catch block to the promise but it then returns pending instead of rejected when the token is invalidated. Which causes the authentication flow to break as my graphql resolvers rely on that rejection to block access to the db.
Fwiw this is how auth0, who I'm using for auth, recommends setting it up. They just don't mention the UnhandledPromiseRejectionWarning.
The code looks like this:
//server def
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
//THE PROMISE IN QUESTION
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
reject(err);
}
if (decoded) {
resolve(decoded);
}
});
});
return {
authUserObj
};
}
},
introspection: true,
playground: true
});
//a graphql resolver that gets the rejection via authUserObj and catches the error
addUser: async (parent, args, {authUserObj}) => {
try {
const AuthUser = await authUserObj;
const response = await User.create(args);
return response;
} catch(err) {
throw new AuthenticationError('You must be logged in to do this');
}
}
That all works... except for that nagging node error I wish to vanquish! So I add a catch block to the promise:
const authUserObj = new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) {
console.log("-------rejected-------", err.message)
reject(err);
}
if (decoded) {
console.log("-------decoded-------")
resolve(decoded);
}
});
}).catch( err => { return err.message});
And now instead of authUserObj returning rejected it's pending and anyone will be able to add a user, which kind of defeats the purpose of auth.
If anyone knows how to catch that error while still rejecting it I'm all ears. Thanks.
The problem is less about the unhandled promise rejection and more about the unhandled promise in general. You try to put a promise inside the context object and then await the promise in the addUser resolver only. In other resolvers, the promise might not be used at all, and when the jwt verification fails for those the rejection will go unhandled. (Also if the resolvers are executed asynchronously, the promise might be rejected before they can handle it).
Instead, the whole context initialisation should be done asynchronously, returning a promise for the context object with the user details. This means that the request will fail before even starting to execute a query:
const server = new ApolloServer({
typeDefs,
resolvers,
context: ({ req }) => {
if (req.headers.authorization) {
const token = req.headers.authorization.split(' ')[1];
return new Promise((resolve, reject) => {
jwt.verify(token, getKey, options, (err, decoded) => {
if (err) reject(err);
else resolve(decoded);
});
}).then(authUser => {
if (authUser) return { authUser };
// else return {};
});
// .catch(err => { … }) - you may chose to ignore verification failure,
// and still return a context object (without an `authUser`)
}
// else return {}; - when not sending the header, no token will be checked at all
},
introspection: true,
playground: true
});
// a graphql resolver that checks for the authUserObj
addUser: async (parent, args, {authUserObj}) => {
if (!authUserObj) { // you might also want to check specific claims of the jwt
throw new AuthenticationError('You must be logged in to do this');
}
const response = await User.create(args);
return response;
}
Just like with try/catch, a .catch() will change the promise chain from rejected to resolved if you just return a normal value from the .catch() handler (or return nothing). When you return a "normal" value, the rejection is considered "handled" and the promise chain becomes resolved with that new value. That's how you handle errors and continue normal processing.
To keep the promise chain rejected, you have to either throw or return a rejected promise. That will keep the promise chain as rejected.
So, if you want authUserObj to keep the promise rejected, then change this:
}).catch( err => { return err.message});
to this:
}).catch( err => { return Promise.reject(err.message)});
or something similar that either throws an error or returns a rejected promise.

Unhandled promise rejection. This error originated either by throwing inside of an async function - NodeJS

I'm very new with NodeJS. I'm trying to create a simple server that has a connection to my mongoDB Atlas database but when I run my server I get this error:
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:8825) [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.
Seems to be a common problem based on what I googled, I added the try/catch but it still isn't working.
'use strict';
//const AWS = require('aws-sdk');
const express = require('express');
const mongoose = require('mongoose');
const uuidv4 = require('uuid/v4');
//exports.handler = (event, context, callback) => {
mongoose.connect(
'mongodb+srv://xxxx:xxxx#cluster0-us8bq.mongodb.net/test?retryWrites=true',
{
useNewUrlParser: true
}
),
() => {
try {
//something
} catch (error) {
console.error(error);
}
};
const connection = mongoose.connection;
connection.once('open', () => {
console.log('🖥 Connection to DB was succesful');
});
const app = express();
app.listen({ port: 4800 }, () =>
console.log(`🚀 Server ready at http://localhost:4800`)
);
Mongoose connect returns promise, and most probably there is an error when it attempts to connect: I would suggest using the async function, to handle DB connection. Here is what I use currently.
const config = require('config').db; // Your DB configuration
const combineDbURI = () => {
return `${config.base}${config.host}:${config.port}/${config.name}`;
};
// Connecting to the database
const connect = async function () {
const uri = combineDbURI(); // Will return DB URI
console.log(`Connecting to DB - uri: ${uri}`);
return mongoose.connect(uri, {useNewUrlParser: true});
};
And then call it within an async function using await:
(async () => {
try {
const connected = await connect();
} catch(e) {
console.log('Error happend while connecting to the DB: ', e.message)
}
})();
Or you can call without await using promise API:
connect().then(() => {
console.log('handle success here');
}).catch((e) => {
console.log('handle error here: ', e.message)
})
Besides, using try catch when using callbacks does not make sense, when you don't have promises, you should use error callbacks to catch errors.
So to answer your question (as others mentioned in the comments):
As connect function returns a promise, you should use catch callback to catch the promise rejection. Otherwise, it will throw Unhandled Promise Rejection.
I hope this will help.
UnhandledPromiseRejectionWarning means that promises should have .catch() like
mongoose.connect(...).catch(err => console.log(err))

UnhandledPromiseRejectionWarning in Express App

I'm currently learning Javascript/Node.js/MEAN stack and I'm following an Express tutorial.
I get the following error:
(node:11524) UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'close' of undefined
(node:11524) [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.
When I hit this route in my browser.
function router(nav) {
adminRouter.route('/')
.get((req, res) => {
const url = 'mongodb://localhost:27017';
const dbName = 'libraryApp';
(async function mongo() {
let client;
try {
client = await mongoClient.connect(url);
debug('Connected correctly to server');
const db = client.db(dbName);
const response = await db.collection('books').insertMany(books);
res.json(response);
} catch (err) {
debug(err.stack);
}
client.close();
}());
});
return adminRouter;
}
Could someone point out the issue and explain what the issue is please.
If this line rejects:
client = await mongoClient.connect(url);
Then, you go to your catch block and after that catch block, you call client.close(). But, client is undefined so client.close() will throw and you are not inside any sort of try/catch at that point. Since you're inside an async function, that throw will turn into a rejected promise which you have no .catch() to handle. Thus, you end up with an unhandled promise rejection.
You should be able to fix it like this:
function router(nav) {
adminRouter.route('/').get(async (req, res) => {
const url = 'mongodb://localhost:27017';
const dbName = 'libraryApp';
let client;
try {
client = await mongoClient.connect(url);
debug('Connected correctly to server');
const db = client.db(dbName);
const response = await db.collection('books').insertMany(books);
res.json(response);
} catch (err) {
debug(err.stack);
// make sure and send some response on errors
res.sendStatus(500);
}
if (client) {
client.close();
}
});
return adminRouter;
}
This makes several changes:
Add if (client) before calling client.close() to protect it from the case where `client never got set.
Make the whole .get() callback be async rather then using an IIFE (not required, just seems cleaner to me)
Send an error status and response in your catch statement so you are always sending an http response of some kind, even in error situations.
If you really wanted to be fail-safe, you could just add another try/catch:
function router(nav) {
adminRouter.route('/').get(async (req, res) => {
const url = 'mongodb://localhost:27017';
const dbName = 'libraryApp';
let client;
try {
client = await mongoClient.connect(url);
debug('Connected correctly to server');
const db = client.db(dbName);
const response = await db.collection('books').insertMany(books);
res.json(response);
} catch (err) {
debug(err.stack);
// make sure and send some response on errors
res.sendStatus(500);
}
try {
if (client) {
client.close();
}
} catch(e) {
console.log("unable to close database connection");
}
});
return adminRouter;
}

Properly terminate NodeJS Async Await

I wrote the following code to connect to MongoDB wrapped in a Promise, but as soon as it is connected, my NodeJS code is still running / not exit properly. May I know what is the proper way to return or terminate it?
function connectDB() {
return new Promise (resolve => {
MongoClient.connect(mongoUrl, function( err, db ) {
_db = db;
resolve(db);
});
})
}
(async function crawl() {
const db = await connectDB();
console.log('You are connected');
})()
This isn't really related to async/await, it also happens without. As #Torazaburo stated in the comments, the db driver maintains an open connection to the database, and this open socket (along with its eventhandlers) prevents node from quitting. Closing the connection helps:
function connectDB() {
return new Promise (resolve => {
MongoClient.connect(mongoUrl, function(err, db) {
if (err) reject(err);
else resolve(db);
});
});
}
(async function crawl() {
const db = await connectDB();
console.log('You are connected');
db.close();
console.log('You are no longer connected. Bye!');
})()
A promise have 2 paramaters: resolve and reject.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
You need to change your promise declaration:
return new Promise ((resolve, reject) => {
// ...
}
After that, add return statement to your promise:
return resolve(db)
Also, i suggest you to add an error log to your promise:
if (err) return reject(err)
And catch this error.
Good luck!

Categories

Resources