Unable to handle promise rejection with mongodb and mongoose - node.js

I have the following code, that basically skips the mongodb document entry stage if the incoming documents are already in the database. However this function causes an UnhandledPromiseRejectionWarning when trying to close the db connection immediately after the async is complete.
async dbInsertMany(asset, timeframe, stream){
try{
const [currentAssetModel, multiStream] = this._getMultiModel(asset, timeframe, stream);
// Don't run the db stage if zero length stream received.
if(!multiStream.length==0){
// Refactor this into a try..catch..finally block.
try{
await currentAssetModel.insertMany(multiStream);
console.log('<SUCCESS>');
}catch(err){
console.log('<ERROR>', err);
}finally{
console.log('__DBINSERTMANY:FINALLY__');
};
}else{
// await sleep(1000);
console.log('[WARNNING] No unique documents to add.');
}
}catch(err){
throw new Error('Application failure has occurred!');
}
}
I'm not sure what I'm missing.
The functions that follow are below:
async function dbCycle(){
try{
let asset = new AssetDatabaseHandler();
try{
await asset.dbInsertMany('BTC/EUR', '5m', demoMultiExchangeStream)
console.log('DB_INSERT_MANY: Finished.');
}catch(error){
console.log('DB_INSERT_MANY: [ERROR]', error);
}
}catch(failure){
console.log('[SEVERE_FAILURE]', failure)
}
};
and
(async () => {
try{
await dbConnection.dbConnect();
console.log('Connected.');
const result = await dbCycle();
console.log('> Finished.');
}catch(err){
console.log('> Failed.', err);
}finally{
// dbConnection.dbDisconnect();
console.log('Disconnected.');
}
})();

The .exists() method is returning a promise, something I have overlooked. Waiting for the promise to be resolved after the query is sent fixed the problem. Because I was not waiting for the promise and closing the server for cases where there was nothing new to be added to the db, the orphaned promises were causing the unhandled exception. Something like the code below, solved the problem:
async checkStates(stream, model){
let state;
let states = [];
for(const item of stream){
const date = new Date(millisecondsToTimestring(item[0]));
const ISODate = date.toISOString();
state = await model.exists({date: ISODate});
states.push(state);
// console.log(item, date, state);
}
return states;
}

Related

mongoose.connection.close() not working correctly in my code

i dont know why but this closing this mongoose connection is not working..
even after doing everything correct i dont know why i am getting this error? i'm calling this function in 1-2 sec, maybe it is because the mongo client takes more time than that to close a connection?
this is my function and mongo is the client i have made in separate file
module.exports.getCoins = async (guildId, userId) => {
return await mongo().then(async (mongoose) => {
try {
console.log("running findone");
const result = await profileSchema.findOne({ guildId, userId });
console.log("result:", result);
let coins = 0;
if (result) {
coins = result.coins;
} else {
console.log("inserting a doc");
await new profileSchema({
guildId,
userId,
coins,
}).save();
}
return coins;
} finally {
await mongoose.connection.close();
}
});
the error it is giving is
MongoExpiredSessionError: Cannot use a session that has ended
at applySession (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\sessions.js:632:16)
at Connection.command (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection.js:186:53)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\sdam\server.js:189:18
at Object.callback (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:267:13)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:475:29
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:403:13
at callback (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connect.js:69:9)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connect.js:137:21
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\auth\scram.js:163:20
at MessageStream.messageHandler (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection.js:474:9)
MongoExpiredSessionError: Cannot use a session that has ended
at applySession (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\sessions.js:632:16)
at Connection.command (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection.js:186:53)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\sdam\server.js:189:18
at Object.callback (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:267:13)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:475:29
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection_pool.js:403:13
at callback (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connect.js:69:9)
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connect.js:137:21
at C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\auth\scram.js:163:20
at MessageStream.messageHandler (C:\Users\bhuvn\OneDrive\Desktop\bhuvnesh\S.D.E\node_modules\mongodb\lib\cmap\connection.js:474:9)

Catch multiple nested asynchronous function errors within a single catch block

The code below is an example of what may take place during development.
With the current code, the outer function may throw an error but in this case wont. However, the nested function WILL throw an error (for examples sake). Once it throws the error it cannot be caught as it is asynchronous function.
Bungie.Get('/Platform/Destiny2/Manifest/').then((ResponseText)=>{
//Async function that WILL throw an error
Bungie.Get('/Platform/Destiny2/Mnifest/').then((ResponseText)=>{
console.log('Success')
})
}).catch((error)=>{
//Catch all errors from either the main function or the nested function
doSomethingWithError(error)
});
What I want is for the outer most function to catch all asynchronous function error's but with this code I cannot. I have tried awaiting the nested function but there may be certain circumstances where it will be quicker to not wait for the function. I also tried to include a .catch() with each nested function but this would require a .catch() for each function that would allhandle the error in the same way e.g. doSomethingWithError().
you only needs return the inner function in the outside function.
see example below:
const foo = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo'), 1000);
});
foo.then((res)=>{
console.log(res)
return new Promise((resolve,reject)=>{
setTimeout(() => reject("bar fail"), 1000);
})
}).catch((e)=>{
// your own logic
console.error(e)
});
this is called promise chaining. see this post for more info https://javascript.info/promise-chaining
if you have multiple promises can do something like:
const foo1 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo1'), 1000);
});
const foo2 = new Promise((resolve,reject) =>{
setTimeout(() => resolve('foo2'), 2000);
});
const foo3 = new Promise((resolve,reject) =>{
setTimeout(() => reject('foo3'), 3000);
});
const bar = new Promise((resolve,reject) =>{
setTimeout(() => resolve('bar'), 4000);
});
foo1
.then((res)=>{
console.log(res)
return foo2
})
.then((res)=>{
console.log(res)
return foo3 // throws the error
})
.then((res)=>{
console.log(res)
return bar
})
.catch((e)=>{
// every error will be cached here
console.error(e)
});
I would aim to use async / await unless you have very particular reasons, since it avoids callback hell and makes your code simpler and more bug free.
try {
const response1 = await Bungie.Get('/Platform/Destiny2/Manifest/');
const response2 = await Bungie.Get('/Platform/Destiny2/Mnifest/');
console.log('Success');
} catch (error) {
doSomethingWithError(error);
}
Imagine each Bungie call takes 250 milliseconds. While this is occurring, NodeJS will continue to execute other code via its event loop - eg requests from other clients. Awaiting is not the same as hanging the app.
Similarly, this type of code is used in many browser or mobile apps, and they remain responsive to the end user during I/O. I use the async await programming model in all languages these days (Javascript, Java, C#, Swift etc).
Try this:
let getMultiple = function(callback, ... keys){
let result = [];
let ctr = keys.length;
for(let i=0;i<ctr;i++)
result.push(0);
let ctr2 = 0;
keys.forEach(function(key){
let ctr3=ctr2++;
try{
Bungie.Get(key, function(data){
result[ctr3] = data;
ctr--;
if(ctr==0)
{
callback(result);
}
});
} catch(err) {
result[ctr3]=err.message;
ctr--;
if(ctr==0)
{
callback(result);
}
}
});
};
This should get all your data requests and replace relevant data with error message if it happens.
getMultiple(function(results){
console.log(results);
}, string1, string2, string3);
If the error causes by requesting same thing twice asynchronously, then you can add an asynchronous caching layer before this request.

Mongoose promise never gets to .then()

I am using q and I have multiple mongoose .exec() promises that never gets to the .then() part of the code, so never allow the q to resolve. Can't figure out why it never comes back.
var defer = q.defer();
var promises = [];
console.log('Exams:', exams.length);
for (var e=0; e<exams.length; e++) {
console.log('Exams:', exams[e]._id);
var newPromise = Pupilexam.find({ _exam: exams[e]._id }).populate('_user').exec()
.then((pupils) => {
console.log("Adding pupils", exams[e]._id);
exams[e].pupils = pupils;
resolve(exams[e]);
})
.catch((err) => {
reject(err);
});
console.log(typeof newPromise);
promises.push(newPromise);
console.log("Promised pushed");
}
q.all(promises).then(function(data){
console.log("q'd all");
defer.resolve(res.status(200).json(exams));
});
return defer;
The Pupilexam.find().exec() never reaches the .then() so the promises never resolve and the defer never resolves. Why would the mongoose find not get to the .then()? What have I missed?
*** UPDATE ***
Even using the built in promises, we get the same issue. The Pupilexams.find() call never comes back.
var promises = [];
for (var e=0; e<exams.length; e++) {
console.log('e:', e);
console.log('Exam', exams[e]._id);
var newPromise = Pupilexam.find({ _exam: exams[e]._id }).populate('_user').exec()
.then((pupils) => {
console.log("Adding pupils", exams[e]._id);
exams[e].pupils = pupils;
})
.catch(handleError(res));
promises.push(newPromise);
}
Promise.all(promises).then((exams) => {
console.log(values);
res.status(200).json(exams)
});
With this method I also get a headers error on the call UnhandledPromiseRejectionWarning: Error [ERR_HTTP_HEADERS_SENT]: Cannot set headers after they are sent to the client
** ADDITIONAL CODE REQUESTED **
function handleError(res, statusCode) {
statusCode = statusCode || 500;
return function(err) {
console.log(err.message);
res.status(statusCode).send(err);
};
}
To answer the updated question regarding the Cannot set headers after they are sent to the client error. Looks like you send a response to the client inside your handleError function. Now, if more than one Pupilexam.find call fails, handleError would be invoked twice, resulting in the mentioned error.
You should move the catch-handler down to the Promise.all call:
const promises = [];
for (const exam of exams) {
const newPromise = Pupilexam
.find({ _exam: exam._id }).populate('_user').exec()
.then((pupils) => {
exam.pupils = pupils;
});
promises.push(newPromise);
}
Promise.all(promises)
.then((exams) => {
res.status(200).json(exams);
})
.catch(handleError(res));
I guess that you are indeed returning your promise but you are returning an empty json.
There are 2 problems with your approach:
You are not returning from your then: should return pupils and it is returning undefined
You are logging values that I don't know what it is
.then((pupils) => {
console.log("Adding pupils", exams[e]._id);
exams[e].pupils = pupils;
// you should return something // return pupils
})
promises.push(newPromise);
Promise.all(promises).then((exams) => {
// ['undefined', 'undefined', ...]
console.log(values);
res.status(200).json(exams)
});
Looks like the answer was that on these two lines the exams[e] is not in scope, because by the time the promise comes back the loop has moved on, so e is wrong and gets too high so it was erroring.
console.log("Adding pupils", exams[e]._id);
exams[e].pupils = pupils;
Only discovered that when I read #eol's message about the catch and decided to catch it properly and output.
it is Look from your code.
//(async) function
for (var e of exams) {
try {
const pupils = await Pupilexam.find({ _exam: exams[e]._id
}).populate('_user').exec().lean()
e.pupils = pupils
}catch((err){
//handleError
};
}
res.status(200).json({data: exams})
maybe that will show you how match are you wrong

How to fix MongoError: Cannot use a session that has ended

I'm trying to read data from a MongoDB Atlas collection using Node.js. When I try to read the contents of my collection I get the error MongoError: Cannot use a session that has ended. Here is my code
client.connect(err => {
const collection = client
.db("sample_airbnb")
.collection("listingsAndReviews");
const test = collection.find({}).toArray((err, result) => {
if (err) throw err;
});
client.close();
});
I'm able to query for a specific document, but I'm not sure how to return all documents of a collection. I've searched for this error, I can't find much on it. Thanks
In your code, it doesn't wait for the find() to complete its execution and goes on to the client.close() statement. So by the time it tries to read data from the db, the connection has already ended. I faced this same problem and solved it like this:
// connect to your cluster
const client = await MongoClient.connect('yourMongoURL', {
useNewUrlParser: true,
useUnifiedTopology: true,
});
// specify the DB's name
const db = client.db('nameOfYourDB');
// execute find query
const items = await db.collection('items').find({}).toArray();
console.log(items);
// close connection
client.close();
EDIT: this whole thing should be in an async function.
Ran into the same issue when I updated the MongoClient from 3.3.2 to the latest version (3.5.2 as of this writing.) Either install only 3.3.2 version by changing the package.json "mongodb": "3.3.2", or just use async and await wrapper.
If still the issue persists, remove the node_modules and install again.
One option is to use aPromise chain. collection.find({}).toArray() can either receive a callback function or return a promise, so you can chain calls with .then()
collection.find({}).toArray() // returns the 1st promise
.then( items => {
console.log('All items', items);
return collection.find({ name: /^S/ }).toArray(); //return another promise
})
.then( items => {
console.log("All items with field 'name' beginning with 'S'", items);
client.close(); // Last promise in the chain closes the database
);
Of course, this daisy chaining makes the code more synchronous. This is useful when the next call in the chain relates to the previous one, like getting a user id in the first one, then looking up user detail in the next.
Several unrelated queries should be executed in parallel (async) and when all the results are back, dispose of the database connection.
You could do this by tracking each call in an array or counter, for example.
const totalQueries = 3;
let completedQueries = 0;
collection.find({}).toArray()
.then( items => {
console.log('All items', items);
dispose(); // Increments the counter and closes the connection if total reached
})
collection.find({ name: /^S/ }).toArray()
.then( items => {
console.log("All items with field 'name' beginning with 'S'", items);
dispose(); // Increments the counter and closes the connection if total reached
);
collection.find({ age: 55 }).toArray()
.then( items => {
console.log("All items with field 'age' with value '55'", items);
dispose(); // Increments the counter and closes the connection if total reached
);
function dispose(){
if (++completedQueries >= totalQueries){
client.close();
}
}
You have 3 queries. As each one invokes dispose() the counter increments. When they've all invoked dispose(), the last one will also close the connection.
Async/Await should make it even easier, because they unwrap the Promise result from the then function.
async function test(){
const allItems = await collection.find({}).toArray();
const namesBeginningWithS = await collection.find({ name: /^S/ }).toArray();
const fiftyFiveYearOlds = await collection.find({ age: 55 }).toArray();
client.close();
}
test();
Below is an example of how Async/Await can end up making async code behave sequentially and run inefficiently by waiting for one async function to complete before invoking the next one, when the ideal scenario is to invoke them all immediately and only wait until they all are complete.
let counter = 0;
function doSomethingAsync(id, start) {
return new Promise(resolve => {
setTimeout(() => {
counter++;
const stop = new Date();
const runningTime = getSeconds(start, stop);
resolve(`result${id} completed in ${runningTime} seconds`);
}, 2000);
});
}
function getSeconds(start, stop) {
return (stop - start) / 1000;
}
async function test() {
console.log('Awaiting 3 Async calls');
console.log(`Counter before execution: ${counter}`);
const start = new Date();
let callStart = new Date();
const result1 = await doSomethingAsync(1, callStart);
callStart = new Date();
const result2 = await doSomethingAsync(2, callStart);
callStart = new Date();
const result3 = await doSomethingAsync(3, callStart);
const stop = new Date();
console.log(result1, result2, result3);
console.log(`Counter after all ran: ${counter}`);
console.log(`Total time to run: ${getSeconds(start, stop)}`);
}
test();
Note: Awaiting like in the example above makes the calls sequential again. If each takes 2 seconds to run, the function will take 6 seconds to complete.
Combining the best of all worlds, you would want to use Async/Await while running all calls immediately. Fortunately, Promise has a method to do this, so test() can be written like this: -
async function test(){
let [allItems, namesBeginningWithS, fiftyFiveYearOlds] = await Promise.all([
collection.find({}).toArray(),
collection.find({ name: /^S/ }).toArray(),
collection.find({ age: 55 }).toArray()
]);
client.close();
}
Here's a working example to demonstrate the difference in performance: -
let counter = 0;
function doSomethingAsync(id, start) {
return new Promise(resolve => {
setTimeout(() => {
counter++;
const stop = new Date();
const runningTime = getSeconds(start, stop);
resolve(`result${id} completed in ${runningTime} seconds`);
}, 2000);
});
}
function getSeconds(start, stop) {
return (stop - start) / 1000;
}
async function test() {
console.log('Awaiting 3 Async calls');
console.log(`Counter before execution: ${counter}`);
const start = new Date();
const [result1, result2, result3] = await Promise.all([
doSomethingAsync(1, new Date()),
doSomethingAsync(2, new Date()),
doSomethingAsync(3, new Date())
]);
const stop = new Date();
console.log(result1, result2, result3);
console.log(`Counter after all ran: ${counter}`);
console.log(`Total time to run: ${getSeconds(start, stop)}`);
}
test();
other people have touched on this but I just want to highlight that .toArray() is executed asynchronously so you need to make sure that it has finished before closing the session
this won't work
const randomUser = await db.collection('user').aggregate([ { $sample: { size: 1 } } ]);
console.log(randomUser.toArray());
await client.close();
this will
const randomUser = await db.collection('user').aggregate([ { $sample: { size: 1 } } ]).toArray();
console.log(randomUser);
await client.close();
client.connect(err => {
const collection = client
.db("sample_airbnb")
.collection("listingsAndReviews");
const test = collection.find({}).toArray((err, result) => {
if (err) throw err;
client.close();
});
});

httpinvoke and pouchdb promise stalling

after making an httpinvoke call, I need to load couchDB but the promise is not passing on.
createDB: function() {
var db = new PouchDB('options');
db.info().then(function (info){
if(info.doc_count <= 10000) {
var db = new PouchDB('options');
db.destroy().then(function(info){
httpinvoke('http://localhost:9080/secure/sync.form','GET');
}).then(function (res){
console.log(JSON.stringify(res)); //This never gets called but if I move this then() block to httpinvoke(xxx).then() it does get called
}).catch(function(err){
console.log(JSON.stringify(err));
});
}
});
}
Promises chain by return values. If you want to make anything meaningful with a promise you have to return it. A promise represents a value + time, your httpinvoke call doesn't return is similar to a synchronus function not returning.
createDB: function() {
var db = new PouchDB('options');
db.info().then(function (info){
if(info.doc_count <= 10000) {
var db = new PouchDB('options');
db.destroy().then(function(info){
return httpinvoke('...','GET'); // NOTE THE RETURN
}).then(function (res){
console.log(res); // console already shows JSON
}); // no need to `catch anyway`, errors are errors let's not suppress.
}
});
}
Also note that promise

Resources