I am trying to make a connection to a database and would like to see that my code stops executing while the connection has not been established yet. So it can print an error/success message to the console at the right time (before the program will output that startup was successful).
My current code to establish the connection is:
dbConnectAsync = async () => {
try {
await mongoose.connect("mongodb://localhost:27017/meetstation", { useNewUrlParser: true });
console.log(SUCCESS_MSG);
} catch (err) {
console.log(ERROR_MSG);
console.log(err.name);
}
}
I know it is possible to put all other code inside the try block but this is not desirable as I plan on moving the function that establishes the database connection to an other file.
Is there any way to call this function that forces other execution of code to wait until this function is done executing?
Within your code you can await dbConnectAsync and then run after the successful connection occurs. So code will appear in a separate try/catch block, but not in the internal try/catch of dbConnectAsync.
async function program() {
try {
await dbConnectAsync();
// code that executes after successful connection
} catch (err) {
// handle connection error
}
}
One change I would mention is to use throw in the catch block of dbConnectAsync so that any consuming code can respond to it. My example above won't receive any errors as a result.
Actually your server can't do much without a database. Therefore the most appropriate reaction in case of an error is just to crash. With top-level await I would just write a module like this:
export * from "mongoose";
import { connect } from "mongoose";
await connect("mongodb://localhost:27017/meetstation", { useNewUrlParser: true });
Then whenever you use mongoose, import it from that file and not from "mongoose" itself. That way, no code will run until the database is ready and in case of an error the server does crash.
You could extract the database setup function into module:
// db.js
let connection;
export async function setup() {
try {
connection = await mongoose.connect('mongodb://localhost:27017/meetstation', { useNewUrlParser: true });
console.log(SUCCESS_MSG);
} catch (err) {
console.log(ERROR_MSG);
console.log(err.name);
}
}
export function getConnection() {
return connection;
}
Init the connection and then start your application:
// main.js
import { setup, getConnection } from './db.js';
(async function bootstrap() {
await setup();
// start your application
// using getConnection()
})();
Related
I'm setting up a mongoDB endpoint with NodeJS. Implementing this backend
I seem to have a problem with the code where the function static async injectDB sets a global variable let restaurants which another function static async getRestaurants accesses, but then it turned into undefined
import mongodb from "mongodb"
const ObjectId = mongodb.ObjectID
let restaurants
export default class RestaurantsDAO {|
static async injectDB(conn) {
if (restaurants) {
return
}
try {
restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
} catch (e) {
console.error(
`Unable to establish a collection handle in restaurantsDAO: ${e}`,
)
}
}
static async getRestaurants({
filters = null,
page = 0,
restaurantsPerPage = 20,
} = {}) {
console.log(restaurants) // undefined
...
getRestaurants is of course called at a much later point than injectDB, if I console.log(restaurants) in that function, it writes out its values. But its undefined when the other function is called. Why is that?
The injectDB function is called at server start, while the getRestaurants is called when someone acceesses the endpoint.
An alternative solution is to open the connection to DB in the getRestaurants function, is that best practice?
See full code for restaurantsDAO.js
Be aware that you cannot know if the await code has finished unless you check for it. It can really help to put console.logs everywhere! See this example:
export default class RestaurantsDAO {
static restaurants
static async injectDB(conn) {
if (RestaurantsDAO.restaurants) {
return
}
try {
console.log("start loading")
RestaurantsDAO.restaurants = await conn.db(process.env.RESTREVIEWS_NS).collection("restaurants")
console.log("finished loading restaurants!")
} catch (e) {
console.error(
`Unable to establish a collection handle in restaurantsDAO: ${e}`,
)
}
}
static showRestaurants() {
if (RestaurantsDAO.restaurants) {
console.log("restaurants are loaded")
} else {
console.log("restaurants not yet loaded")
}
}
}
So if you call injectDB anywhere else in your code, that doesn't guarantee that restaurants is filled right away.
import RestaurantsDAO from "./restaurantsdao.js"
RestaurantsDAO.injectDB(..) // console: "start loading"
RestaurantsDAO.showRestaurants() // console: "restaurants not yet loaded"
// console: "finished loading" (because await has finished)
BTW I think it makes more sense if you make the restaurants variable part of the class, instead of defining it outside of the class on the module.
I want my nodejs app to not continue unless it connects to mongodb.
I tried:
//Mongo
async function mongoConnect(mongoDB) {
var mongoConnectionSuccessful = false;
LOG("Connecting to mongodb at " + mongoDB + "...");
while (!mongoConnectionSuccessful) {
try {
await mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true });
LOG("connected!");
mongoConnectionSuccessful = true;
mongoose.connection.on('error', ()=>LOG('MongoDB connection error:'));
return;
} catch (error) {
LOG(error);
}
await utils.sleep(500);
}
}
mongoConnect(config.mongoUrl);
but in order to use await in mongoose.connect, I must make mongConnect async, but I then cannot call it in a blocking way from the code because in order to call like this, I must call with await, but await is only permitted inside async functions.
I must call with await, but await is only permitted inside async functions
That's correct. So do just that:
async function main () {
await mongoConnect(config.mongoUrl);
// rest of your code...
}
main();
For example if this is an Express server do something like:
const express = require('express');
const app = express();
async function main () {
await mongoConnect(config.montoUrl);
const routes = require('./my-routes');
app.use(routes);
app.listen(config.port);
}
main();
You want to attempt reconnect if mongoose fails to connect. Here is an example logic without helper lib.Props to the guy who posted this solution in a github issue for mongoose. Here
function createConnection (dbURL, options) {
var db = mongoose.createConnection(dbURL, options);
db.on('error', function (err) {
// If first connect fails because mongod is down, try again later.
// This is only needed for first connect, not for runtime reconnects.
// See: https://github.com/Automattic/mongoose/issues/5169
if (err.message && err.message.match(/failed to connect to server .* on first connect/)) {
console.log(new Date(), String(err));
// Wait for a bit, then try to connect again
setTimeout(function () {
console.log("Retrying first connect...");
db.openUri(dbURL).catch(() => {});
// Why the empty catch?
// Well, errors thrown by db.open() will also be passed to .on('error'),
// so we can handle them there, no need to log anything in the catch here.
// But we still need this empty catch to avoid unhandled rejections.
}, 20 * 1000);
} else {
// Some other error occurred. Log it.
console.error(new Date(), String(err));
}
});
db.once('open', function () {
console.log("Connection to db established.");
});
return db;
}
// Use it like
var db = createConnection('mongodb://...', options);
and with a lib promise-retry
const promiseRetry = require('promise-retry')
const options = {
useNewUrlParser: true,
reconnectTries: 60,
reconnectInterval: 1000,
poolSize: 10,
bufferMaxEntries: 0 // If not connected, return errors immediately rather than waiting for reconnect
}
const promiseRetryOptions = {
retries: options.reconnectTries,
factor: 2,
minTimeout: options.reconnectInterval,
maxTimeout: 5000
}
const connect = () => {
return promiseRetry((retry, number) => {
logger.info(`MongoClient connecting to ${url} - retry number: ${number}`)
return MongoClient.connect(url, options).catch(retry)
}, promiseRetryOptions)
}
module.exports = { connect }
I found the solution in this article
I beleive what you need is to promise your call of mongoConnect(config.mongoUrl),
than await promises until it is called back.
async function getConcurrently() {
let promises = [];
promises.push(mongoConnect(config.mongoUrl))
// promises.push(getUsers());
// promises.push(getCategories());
// promises.push(getProducts());
let mongo = await Promise.all(promises);
//let [users, categories, products] = await Promise.all(promises);
}
Please Note this warning in the article:
As the first example, first we create an array of Promises (each one of the get functions are a Promise). Then, we execute all of them concurrently and simultaneously, awaiting for all of them to finish (await Promise.all). Finally, we assign the results to the respective variables users, categories and products. Despite the fact that it works, it’s important to say that using Promises.all() for everything is a bad idea.
I assume that for some reason you have incoming events meanwhile you are attempting to connect to mongo. My approach would be preventing any incoming events (such as starting the server and setting listeners) before connection to mongo. If it's not possible, one way to do it is to use a self-invoking function.
//Mongo
async function mongoConnect(mongoDB) {
var mongoConnectionSuccessful = false;
LOG("Connecting to mongodb at " + mongoDB + "...");
while (!mongoConnectionSuccessful) {
try {
await mongoose.connect(mongoDB, { useNewUrlParser: true, useUnifiedTopology: true });
LOG("connected!");
mongoConnectionSuccessful = true;
mongoose.connection.on('error', ()=>LOG('MongoDB connection error:'));
return;
} catch (error) {
LOG(error);
}
await utils.sleep(500);
}
}
(async function() {
// do stuff
await mongoConnect(config.mongoUrl);
// do other stuff
})();
I have a simple postgres.js wrapper file.
const pg = require("pg")
const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL });
function close() {
return pool.end()
}
module.exports = {
end: pool.end,
close
};
Running a jest testcase that utilizes the postgres library above, like so:
const postgres = require("./path/to/postgres.js");
describe("Foo", () => {
afterAll(() => {
return postgres.end();
})
it(...)
});
Will yield the "This usually means that there are asynchronous operations that weren't stopped in your tests." error message and hangs there.
However, if I change the line postgres.end() to postgres.close(), it correctly closes the DB connection and jest terminates after the test is done.
My question is that doesn't close and end functionally does the same thing? Why does one close the connection but the other doesn't?
Your end function is just executing the pool.end and not returning it as a promise, so it's not similar.
For a better visualization, your current code is basically doing this:
function close() {
return pool.end()
}
function end() {
pool.end()
}
module.exports = {
end,
close
};
So, I'm new to Nodejs, and now I am trying to make an insert, based on an action I receive from a client.
I have a functions module, which is called by the routes to make certain tasks. Each action needs to be recorded in a mssql table, so I chose to use mssql from npm.
https://www.npmjs.com/package/mssql
Each function in the functions module calls the saveActionToDB function which received an action and a username to make the insert into the table like this:
function saveActionToDB(action, user){
if (config.Logging.DB.type == 'mssql'){
const dbOptions = {
user: config.Logging.DB.user,
password: config.Logging.DB.password,
server: config.Logging.DB.server,
database: config.Logging.DB.database,
options: {
encrypt: config.Logging.DB.encrypt
}
};
const database = require('mssql');
async () => {
try{
const pool = await database.connect(dbOptions);
const result = await database.query(`insert into actions (action, user) values ("${action}", "${user}")`);
console.log(pool);
}
catch (err){
console.log(err);
combinedLogger.error(err);
}
}
}
else if(config.Logging.DB.type == 'oracle'){
//oracle
}
}
The app needs to have the ability to use either mssql or oracle. That;s why it checks the config.Logging.DB.type val to use each of the two.
Right now, the functions call the saveActionToDB, but it doesn't do anything. No errors either. I am guessing it's an issue with the async thing.
Note that I don't need to wait for the saveActionToDB to end in order to respond to the client.
Can anyone help?
You are not calling your async function. This just declares a function but does not execute. async () => {
Looks at this example
console.log('a');
x = async () => {
console.log('b');
}
console.log('c');
x();
the output is a c b. However if I do
console.log('a');
async () => {
console.log('b');
}
console.log('c');
Output is just a c.
async function(req, res) {
try {
const user = await userCtrl.getUser();
const userMaps = await mapsCtrl.findDetails(user.mapId);
res.send(userMaps);
} catch (error) {
//handle error
res.status(400).send(error)
}
}
// user controll
function getUser() {
return new Promise(function(resolve, reject) {
//data base read using mysql
req.app.get("mysqlConn").query(query, function(error, results, fields) {
if (error) {
reject(error);
}
resolve(results);
});
})
}
//maps controller function is also like above one.
This is the code handle part of an express get route. Sometimes the rejected code is not getting caught. I get the error returned from MySQL in 200 status code.
Yes you can write multiple awaits in a single try catch block. so your catch block will receive the error if any of the above await fails.
Refer this link for more information about async-await - https://javascript.info/async-await
reject() or resolve () doesn't mean the functions is terminated. So we have to explicitly return from the function to avoid further code execution. In your case put resolve in else block or just put return statement in the if block after the reject is called!
Refer this link for more information:- Do I need to return after early resolve/reject?
I hope this help :)
Regards.