So i am novice at Node.js, and have been checking out its mongoose package. I have a mongodb collection with a document with field name = "Test Entry"
I have tried executing a .findOneAndDelete() method call on a model wrapped within a timeout, wrapped within a function.
One issue that baffles me is why this piece of code
function deleteEntry() {
setTimeout(function () {
Test_Model.findOneAndDelete({
name: "Test Entry",
});
}, 5000);
}
deleteEntry();
does not delete the entry within mongodb
whereas
function deleteEntry() {
setTimeout(async function () {
await Test_Model.findOneAndDelete({
name: "Test Entry",
});
}, 5000);
}
deleteEntry();
this one does.
will the mongoose method not execute unless there is a callback made of some kind. No matter if it's for vain?
There were mentions of something similar in the documentation . Does the actual execution of the command occur only when there is a callback/await/then ?
"Why does not calling this operation NOT trigger the task?" - I think MongoDB driver check if you passed a callback (as argument) or not, If you didn't pass callback, Instead, It return Promise. So no matter what, You operation will get executed.
Also you should handle Error in setTimeout function, Because It don't handle any error or error from any async operation.
Try this example:
let delay = ms => new Promise(ok => setTimeout(ok, ms));
async function deleteEntry() {
await delay(5000);
return Test_Model.findOneAndDelete({ name: "Test Entry" });
}
deleteEntry()
.then(result => console.log(result))
.catch(err => console.log(err));
Related
I have the following situation:
Why does the clients variable resolve in undefined when I call it below;
function getClientList() {
TeamSpeak.connect({
host: "localhost",
serverport: 9987,
nickname: "NodeJS Query Framework",
}).then(async (teamspeak) => {
const clients = await teamspeak.clientList({ clientType: 0 });
if (clients) return clients;
else return null;
});
}
When I call it like that it resolves as undefined
const clients = getClientList();
The short answer to your question is that you cannot use a resulting variable until the Promise resolves. Whether you use .then or await it's really the same mechanism and you can't access the result of your call until it's ready.
You can only get the results of it by waiting for the Promise to resolve. This can be done by using await if your function is marked as async, or by using the .then() function on the Promise since it will get called after the result is ready.
Think of the Promise as a black box that has a then function in it. Remember that each .then() function also returns as a Promise object which is why you can chain them as promise.then(...).then(...).then(...);. Your code then looks like this:
function getClientList() {
PROMISE1.then(someFunction);
}
const clients = getClientList();
Note that even though it spans multiple lines, your getClientList function is really just one line of code TeamSpeak.connect(...).then(...);
There are a few things wrong with your code. Let's go through them one by one:
The getClientList function doesn't return anything explicitly. In its most basic form it's function getClientList() { someCall(); } and that returns undefined by default. That's why clients is always undefined.
If you add a return statement to the function, it will return a Promise and might look like this:
function getClientList() {
return PROMISE1.then(someFunction);
}
const clients = getClientList();
This is better because clients is not undefined any more, but it will be the Promise that may not have finished its work yet. To see the result of the call you'd need to use the then function like clients.then(result => console.log(result)) to allow it to wait for the asynchronous work to complete.
You're using both .then and async/await. It will probably be cleaner and easier to work with if you go with one or the other (await is my preference). Your code can look like this:
async function getClientList() {
const teamspeak = await TeamSpeak.connect({
host: "localhost",
serverport: 9987,
nickname: "NodeJS Query Framework",
});
const clients = await teamspeak.clientList({ clientType: 0 });
return clients || null; // returns as a Promise resolving to clients or null
}
const clients = await getClientList(); // assuming you're in another async fn
There is no error handling. If any of the asynchronous calls fail then the Promise will either reject or cause an exception. You should handle that by either surrounding it with a try/catch block at some level, or by adding .catch() after the final then, or by adding a second function to the .then() call to handle a rejection.
So putting it all together your code could look like this:
async function getClientList() {
const teamspeak = await TeamSpeak.connect({
host: "localhost",
serverport: 9987,
nickname: "NodeJS Query Framework",
});
return await teamspeak.clientList({ clientType: 0 }) || null;
}
// Example calling getClientList using ".then"
getClientList()
.then(
client => {
/* use the client value in this scope */
},
reason => {/* report the reason for rejection */}
) // end of .then
.catch(err => {/* report the error */});
// Example calling getClientList using "await"
try {
const client = await getClientList();
/* use the client value in this scope */
} catch (err) {/* report the error */}
No matter how you make the call, you're only able to access the clients value (returned by calling the clientList function) within the scope of either the .then or after await.
When you call getClientList() the inner "connect" function is started instantly in your library while the "then" callback is left to be executed in a future you can't know (when your library has completed the task triggered in "connect").
Preparing this "then" callback is done instantly, so getClientList() ends without returning anything.
The "return" instructions inside "then" are not returns for getClientList() but for the one who executes the callback (the code in your library).
What you can do is promissify the process: Return a promise that can be waited for and will be solved in the future when callback has been executed.
Try it like this:
function getClientList() {
// Tell the function caller that he will have the result at some time in the future.
// In other words: return a promise.
return new Promise( (resolve,reject) => {
TeamSpeak.connect({
host: "localhost",
serverport: 9987,
nickname: "NodeJS Query Framework",
}).then(async (teamspeak) => {
const clients = await teamspeak.clientList({ clientType: 0 });
// Give the caller that is waiting by holding the promise the desired result.
if (clients) resolve(clients);
// Throw error to the caller that is waiting by holding the promise .
else reject('Connect failed');
});
});
}
But notice you must await for the promise inside an "async" function. Otherwise you would block the main thread.
async function main() {
const clients = await getClientList();
}
Do set a try/catch as the promise might throw an error if "reject" is executed:
async function main() {
try{
const clients = await getClientList();
} catch(err){
console.log(err);
// Will show:
// Connect failed
}
}
I was writing a script to pull data from Google Cloud metrics via API when I accidentally discovered that I don't know how to properly catch errors of asynchronous functions. :O
Here is the example code from google cloud:
// Imports the Google Cloud client library
const monitoring = require('#google-cloud/monitoring');
// Creates a client
const client = new monitoring.MetricServiceClient();
/**
* TODO(developer): Uncomment and edit the following lines of code.
*/
const projectId = 'XXXXXXXXX';
async function getMetrics() {
const request = {
name: client.projectPath(projectId),
filter: 'metric.type="cloudsql.googleapis.com/database/cpu/utilization"',
interval: {
startTime: {
// Limit results to the last 20 minutes
seconds: Date.now() / 1000 - 60 * 1,
},
endTime: {
seconds: Date.now() / 1000,
},
},
// Don't return time series data, instead just return information about
// the metrics that match the filter
view: 'HEADERS',
};
// Writes time series data
console.log('start')
const [timeSeries] = await client.listTimeSeries(request);
console.log('Found data points for the following instances:');
timeSeries.forEach(data => {
console.log(data.metric.labels.instance_name);
});
}
getMetrics();
The function listTimeSeries returns a promise. I got an error that I need to be authenticated to perform that action, no problem there.
The issue is that I couldn't catch that error.
I tried surrounding the call with try {...} catch (err) {...} block, wasn't caught.
I tried to catch it like this const [timeSeries] = await client.listTimeSeries(request).catch(console.log); - No luck there.
I must be missing something because I'm pretty new to nodeJS and no way catching errors from async functions is not supported.
I'm using nodeJS v14.
What am I missing guys?
Thank you in advance!
EDIT
As requested (by #CherryDT), here is the full error output:
I hope its not too blurry.
EDIT
It turns out that the way I've been trying to catch errors is fine.
The issue occurred because of listTimeSeries function (from an external library), which threw an error instead of rejecting the promise, which is impossible to catch.
Thanks, guys.👍
Note that I refer to "async functions" and "asynchronous functions." In Javascript "async function" means a function created with the async keyword, whereas when I say "asynchronous function" I mean in the traditional sense, any function that runs asynchronously. In Javascript, functions created with the async keyword are actually just promises under the hood.
Your code would work if errors thrown from asynchronous functions (inside promises) could be caught. Unfortunately, they can't. Unless the function is using the async function syntax, errors in promises must be wrapped with reject. See the MDN example for the gotcha we're looking at here:
// Throwing an error will call the catch method most of the time
var p1 = new Promise(function(resolve, reject) {
throw new Error('Uh-oh!');
});
p1.catch(function(e) {
console.error(e); // "Uh-oh!"
});
// Errors thrown inside asynchronous functions will act like uncaught errors
var p2 = new Promise(function(resolve, reject) {
setTimeout(function() {
throw new Error('Uncaught Exception!');
}, 1000);
});
p2.catch(function(e) {
console.error(e); // This is never called
});
// Errors thrown after resolve is called will be silenced
var p3 = new Promise(function(resolve, reject) {
resolve();
throw new Error('Silenced Exception!');
});
p3.catch(function(e) {
console.error(e); // This is never called
});
I believe this is the code in the library that's throwing the error, below. Notice that another error is being properly rejected. All comments are mine.
for (const methodName of metricServiceStubMethods) {
const callPromise = this.metricServiceStub.then(
stub => (...args: Array<{}>) => {
if (this._terminated) {
// This is the right thing to do!
return Promise.reject('The client has already been closed.');
}
const func = stub[methodName];
return func.apply(stub, args);
},
(err: Error | null | undefined) => () => {
// If this was an async function (as in, using the keyword async,
// not just literally an asynchronous function), this would work,
// because the async keyword is just syntactic sugar for creating
// a promise. But it's not so it can't be caught!
throw err;
}
);
I believe, in this case, unfortunately there's no way for you to catch this error.
You can do this.
(async function() {
try {
await getMetrics();
} catch(error) {
console.log("Error occured:", error);
}
})();
Please note that if you are trying to catch the error in Promise you can use .then(() => { }).catch(err => { }) style, but for async/await you will need try { } catch(err) { } style to catch the error.
Edit
By doing this, it must catch any errors if the promise become rejected. If you still cannot catch the error, this means that the library you're using doesn't reject the promise properly (Promise.reject()), instead it did hard-coded throw error inside the promise instead of rejecting one. For this case you can't do anything with error catching.
I am trying to rewrite a module I wrote that seeds a MongoDB database. It was originally working fine with callbacks, but I want to move to Promises. However, the execution and results don't seem to make any sense.
There are three general functions in a Seeder object:
// functions will be renamed
Seeder.prototype.connectPromise = function (url, opts) {
return new Promise((resolve,reject) => {
try {
mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
resolve(connected)
})
} catch (error) {
reject(error)
}
})
}
[...]
Seeder.prototype.seedDataPromise = function (data) {
return new Promise((resolve,reject) => {
if (!this.connected) reject(new Error('Not connected to MongoDB'))
// Stores all promises to be resolved
var promises = []
// Fetch the model via its name string from mongoose
const Model = mongoose.model(data.model)
// For each object in the 'documents' field of the main object
data.documents.forEach((item) => {
// generates a Promise for a single item insertion.
promises.push(promise(Model, item))
})
// Fulfil each Promise in parallel
Promise.all(promises).then(resolve(true)).catch((e)=>{
reject(e)
})
})
}
[...]
Seeder.prototype.disconnect = function () {
mongoose.disconnect()
this.connected = false
this.listeners.forEach((l) => {
if (l.cause == 'onDisconnect') l.effect()
})
}
There is no issue with the main logic of the code. I can get it to seed the data correctly. However, when using Promises, the database is disconnected before anything else is every done, despite the disconnect function being called .finally().
I am running these functions like this:
Seeder.addListener('onConnect', function onConnect () { console.log('Connected') })
Seeder.addListener('onDisconnect', function onDisconnect () {console.log('Disconnected')})
Seeder.connectPromise(mongoURI, options).then(
Seeder.seedDataPromise(data)
).catch((error) => { <-- I am catching the error, why is it saying its unhandled?
console.error(error)
}).finally(Seeder.disconnect())
The output is this:
Disconnected
(node:14688) UnhandledPromiseRejectionWarning: Error: Not connected to MongoDB
at Promise (C:\Users\johnn\Documents\Code\node projects\mongoose-seeder\seed.js:83:37)
which frankly doesn't make sense to me, as on the line pointed out in the stack trace I call reject(). And this rejection is handled, because I have a catch statement as shown above. Further, I can't understand why the database never even has a chance to connect, given the finally() block should be called last.
The solution was to return the Promise.all call, in addition to other suggestions.
You are passing the wrong argument to then and finally. First here:
Seeder.connectPromise(mongoURI, options).then(
Seeder.seedDataPromise(data)
)
Instead of passing a callback function to then, you actually execute the function on the spot (so without waiting for the promise to resolve and trigger the then callback -- which is not a callback).
You should do:
Seeder.connectPromise(mongoURI, options).then(
() => Seeder.seedDataPromise(data)
)
A similar error is made here:
finally(Seeder.disconnect())
It should be:
finally(() => Seeder.disconnect())
Promise Constructor Anti-Pattern
Not related to your question, but you are implementing an antipattern, by creating new promises with new Promise, when in fact you already get promises from using the mongodb API.
For instance, you do this here:
Seeder.prototype.connectPromise = function (url, opts) {
return new Promise((resolve,reject) => {
try {
mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
resolve(connected)
})
} catch (error) {
reject(error)
}
})
}
But the wrapping promise, created with new is just a wrapper that adds nothing useful. Just write:
Seeder.prototype.connectPromise = function (url, opts) {
return mongoose.connect(url, opts).then(() => {
const connected = mongoose.connection.readyState == 1
this.connected = connected
return connected;
});
}
The same happens in your next prototype function. I'll leave it to you to apply a similar simplification there, so avoiding the promise constructor antipattern.
In the later edit to your question, you included this change, but you did not return a promise in that function. Add return here:
return Promise.all(promises).then(() => {
//^^^^^^
return true
}).catch(() => {
console.log(`Connected:\t${this.connected}`)
})
I am setting up testing using Jest for an Node/Express/Mongo project. I have tried to write a function to clear collections so each test starts with a clean slate:
const clearCollection = (collectionName, done) => {
const collection = mongoose.connection.collections[collectionName]
collection.drop(err => {
if (err) throw new Error(err)
else done()
)
}
beforeEach(done => {
clearCollection('users', done)
})
And another try, with promises:
const clearCollection = collectionName => {
const collection = mongoose.connection.collections[collectionName]
return collection.drop()
}
beforeEach(async () => {
await clearCollection('users')
})
The problem is that it they both alternate between working and throwing an error. Every time I save the file, it either works perfectly, or throws an error, alternating each time. The errors are always either one of:
MongoError: cannot perform operation: a background operation is currently running for collection auth_test.users
MongoError: ns not found
I can get it to work 100% of the time (limited by the stack anyway) by making clearCollection() call itself inside a catch(), but this feels so wrong:
const clearCollection = collectionName => {
const collection = mongoose.connection.collections[collectionName]
return collection.drop()
.catch(() => clearCollection(collectionName))
}
I don't know why mongoose.connection.collections.<collection>.drop() randomly throws errors, but there is a simple way to remove all the documents in Mongoose, which works just fine for resetting the collection before tests:
beforeAll(async () => {
await User.remove({})
})
Works every time.
I was having a similar issue while trying to drop the database in the beginning of the tests and populating the database right after. In the first run, collections would be created; in the next one, I would get an error of 'database is in the process of being dropped.'; ...and it was alternating like this.
I was also using an "in memory" Mongodb, running $ run-rs -v 4.0.0 -s (https://www.npmjs.com/package/run-rs) in a separate terminal window before running my Mocha tests. Also, I have Mongoose 5.2.0 and Mocha 5.1.1 here.
I've found out that Mongoose not necessarily executes the the drop commands immediately. Instead, it will schedule them for when the connection is up.
So, there can be a race condition in which the promise of the drop command is resolved and you move on in the code until reaching the instruction for creating your collections... but the drop command didn't finish running yet, and you'll get the error for the creation the new collections. The drop finish running and for the next time you run your test, your database (or collection) is already dropped, and hence you'll be able to insert the new collections again.
So, this is how I've solved...
Run in the before hook:
test.dropDb = function(done) {
this.timeout(0)
let database = your_MongoDB_URI
mongoose.connect(database, function (err) {
if (err) throw err;
mongoose.connection.db.dropDatabase(function(err, result){
console.log('database dropping was scheduled');
});
})
mongoose.connection.on('connected', function() {
setTimeout(function(){ done() }, 3000);
});
}
Run in an nested before hook
test.createDb = function(done) {
this.timeout(0)
let createDb = require('./create-db.js')
createDb() // here I call all my MyCollections.insertMany(myData)...
.then( function() {
done()
})
.catch( function(err) {
assert( null == err)
});
}
Run in the after hook
test.afterAll = function(done) {
mongoose.connection.close()
.then( function() {
done()
})
}
I don't know why I had to explicitly close the connection in the after hook. I guess it has something to do with the way run-rs is programmed, but I didn't look into it. I just know that, in my case, closing the connection after the test was mandatory to get the expected behavior.
I have a test that tests an external module which updates the database asynchronously and then immediately I use the (updated) values for the next call. This is done by chaining promises like below.
var expectedCateg = 'expected';
ExternalModule.loadUsers(2)
.then(function firstCallback(){
return ExternalModule.updateDBAsync(db, expectedCateg);
})
.then(function secondCallback(updatedRecord){
return ExternalModule.readDBAsync(db, categ);
})
.then(function thirdCallback(resultCateg){
if (resultCateg == expectedCateg) {
console.log("hurray")
}
})
The ExternalModule that updates the DB async has a few calls like this:
exports.updateDBAsync(db, expectedCateg) {
return Db.getMyDb()
.updateAsync(
{ $and: [{"keyA": keyA}, {"keyB": keyB}],
"$isolated": true,
},
{ $set: {"categ": expectedCateg}},
{multi: false})
.then(function success (results) {
return results
})
}
This should log "hurray" but it does not, however, if I execute it and then I look in the database manually, using robomongo, for instance, it is updated. This means that when secondCallback is called, the database update operation has not finished.
How is this possible? Isn't the point of promises to resolve only when the operation is finished? How can we ensure that control is passed on only after the database update is actually finished?