nodejs async module exports - node.js

I am exporting a config js object asynchronously. How can I use the exported object in another module asynchronously?
module.export = (async function(){
connConf = await getDbConnectionConfiguration('administration_db');
const config = {
development: {
username: connConf.user,
password: connConf.password,
host: connConf.host,
database: connConf.database}
};
return config;
})();
Then I am importing the above module in another object as below,
const configs = await require('../config');
But I am getting the error message saying Unexpected reserved word 'await'

You have a typo in your config module: module.export should be module.exports.
Also, you are immediately calling your function on export and returning a pending Promise. It is better to remove last parenthesis and return the function itself:
//fixed typo V
module.exports = (async function(){
connConf = await getDbConnectionConfiguration('administration_db');
const config = {
development: {
username: connConf.user,
password: connConf.password,
host: connConf.host,
database: connConf.database
}
};
return config;
}); // <-- parenthesis removed here
Unexpected reserved word 'await' means that you are using await outside async function ("top-level awaits") and it can't be done in CommonJS (.js) modules.
One workaround is to wrap your code in an async function and call it:
const configsModule = require('./config'); // <-- your imported async function
async function init() {
const configs = await configsModule();
// all you code should be here now
console.log(configs);
}
// call init
init();
Another way is to use top-level awaits if your Node is version 14 or above and you switch to ES modules (.mjs):
// app.mjs
import configsModule from '../config';
const configs = await configsModule();

Related

Node async await not waiting

When I run the below code and remove the setTimeout all the methods run asynchronously even with the await. Any ideas why the await does not wait.
const dotenv = require("dotenv");
dotenv.config({ path: "./config/.env" });
const { setTimeout } = require("timers/promises");
const module1 = require("./utils/module1");
const module2 = require("./utils/module2");
const module3 = require("./utils/module3");
const run = async () => {
await module1.import();
await setTimeout(60000);
await module2.import();
await setTimeout(120000);
await module3.import();
};
run();
Example of a module:
exports.import = async () => {
//do something
}
As #robertklep stated in the comments the problem is inside the actual method of the module.
Nothing was properly awaited inside the first method causing the second method to kick off.
Specifically I was incorrectly using readline and had to change the code as follows:
instead of
rl.on()
use
for await (const line of rl) { }

Importing a specific function present in a file that contains a connection to the mongoose db crashes when running tests

I have a NodeJs app using express and MongoDB that I want to write unit tests for using Jest.
Here's the problem: I'm importing a specific function from the file reports.js. The function itself doesn't use the mongoose db, but I know that using require executes the entire file before returning the exported objects. That being said, when I'm running my test file, the "testing" part works fine, but I'm getting the reference error below since the importation is still in process when my tests are completed.
ReferenceError: You are trying to import a file after the Jest environment has been torn down. From tests/reportsLib.test.js.
I've done some research and a lot suggests to use jest.useFakeTimers() before running each tests. But the same error occurs. However, when I use jest.useFakeTimers() after importing mongoose in the payment.js file (which is not optimal since I would like to have everything about tests in the tests files), the error is fixed but another occurs on the following line: campaignstatsSchema.plugin(mongoose_delete, {deletedAt:true, overrideMethods: true})
TypeError: Invalid schema configuration: -ClockDate- is not a valid type at path -deletedAt-. See mongoose-schematypes for a list of valid schema types.
Another way to fix the 1st error is to import the function in a beforeAll() Jest function, but the 2nd error still occurs.
Is there some sort of refac needed for the way I'm connecting to my DB and creating my schemas/models? What's the best way to solve this issue? Thanks in advance!
reports.tests.js file:
const { functionToTest } = require('./../lib/reports');
describe('report.js testing', () => {
beforeEach(() => {
jest.useFakeTimers();
});
it('should return a value of 0', () => {
expect(functionToTest()).toBe(0);
});
});
reports.js file:
const db = require('./../services/db');
// ...other imports
const functionToTest = function() {
return 0;
};
const otherFunction = async function(report_id, report_data) {
//some code
await db.report.findOneAndUpdate({_id: report_id}, {data:report_data});
};
module.exports = {
functionToTest,
// other functions
};
db.js file:
const mongoose = require('mongoose');
const payment = require('../models/payment');
const report = require('../models/report');
mongoose.connection.setMaxListeners(0);
mongoose.connect(process.env.MONGODB_URI, {
useNewUrlParser: true,
useUnifiedTopology: true,
useFindAndModify: false,
useCreateIndex: true
});
const generateObjectId = function() {
return mongoose.Types.ObjectId();
};
const getTimestampFromId = function(id) {
return mongoose.Types.ObjectId(id).getTimestamp();
};
module.exports = {
report,
payment,
// other models
};
payment.js file:
const mongoose = require('mongoose');
const mongoose_delete = require('mongoose-delete');
const mongoose_history = require('mongoose-history');
const paymentProfileSchema = mongoose.Schema({
// fields here
});
paymentProfileSchema.plugin(mongoose_history);
paymentProfileSchema.plugin(mongoose_delete);
const paymentProfile = mongoose.model('payment-profiles', paymentProfileSchema);
paymentProfile.syncIndexes();
module.exports = paymentProfile;

Wait for the promise

I'm trying to use the code from this answer to extend one OSS application.
However app.js is sync and no matter what I do, I cant force it to wait for the promise to resolve.
app.js
var cosmos = require('./cosmos.js');
const key = cosmos.key(var1, var2, var3);
console.log(key); // << shows Promise { <pending> }
mongoose.connect(`redacted`, {
auth: {
username: config.database.name,
password: key
}
});
cosmos.js
async function retriveKey(subId, resGrp, server) {
const { EnvironmentCredential } = require("#azure/identity");
const { CosmosDBManagementClient } = require("#azure/arm-cosmosdb");
const armClient = new CosmosDBManagementClient(
new EnvironmentCredential(), subId
);
const { primaryMasterKey } = await armClient.databaseAccounts.listKeys(
resGrp, server
);
return new Promise(resolve => {
setTimeout(() => resolve(primaryMasterKey), 1000);
});
}
exports.key = retriveKey
If i console.log() inside the async function it actually shows the key, however mongoose db connection doesn't wait for the promise to get resolved, it starts connecting straight away and fails with something like: password must be a string.
If i hardcode actual key instead of this promise - everything works fine.
EDIT:
halfway there:
// pull cosmos keys
async function retriveKey(subId, resGrp, server) {
const { EnvironmentCredential } = require("#azure/identity");
const { CosmosDBManagementClient } = require("#azure/arm-cosmosdb");
const armClient = new CosmosDBManagementClient(
new EnvironmentCredential(), subId
);
const { primaryMasterKey } = await armClient.databaseAccounts.listKeys(
resGrp, server
);
return primaryMasterKey // don't even need a promise here
}
exports.key = retriveKey
var mongooseConnected; // global variable
app.use(function (req, res, next) {
if (!moongooseConnected) {
moongooseConnected = cosmos.key(var1, var2, var3).then(function (key) {
mongoose.connect(`xxx`,
{
auth: {
username: config.database.name,
password: key
}
}
);
console.log(key); // works as expected
require('./models/user');
require('./models/audit');
require('./routes/user')(app);
require('./routes/audit')(app, io);
});
}
moongooseConnected.then(function () {
next();
});
});
the database connection gets established, console.log(key) shows proper key in the log, however no routes are present in the app.
if i move routes or models outside of this app.use(xyz) - i'm starting to see failures due to:
Connection 0 was disconnected when calling createCollection
or
MongooseError [MissingSchemaError]: Schema hasn't been registered for model "User".
which (i assume) means they require mongoose to be instantiated, but they are not waiting.
If you switch from CommonJS modules to ES modules, you can use await to wait for a promise to resolve:
import cosmos from './cosmos.js';
const key = await cosmos.key(var1, var2, var3);
console.log(key);
await mongoose.connect(`redacted`, {
auth: {
username: config.database.name,
password: key
}
});
Alternatively, you can wait with the initialization of mongoose until the first request comes in, because express middleware is asynchronous:
var mongooseConnected; // global variable
function connectMongoose() {
if (!mongooseConnected)
mongooseConnected = cosmos.key(var1, var2, var3)
.then(key => mongoose.connect(`redacted`, {
auth: {
username: config.database.name,
password: key
}
}));
return mongooseConnected;
}
module.exports = connectMongoose;
If the code above is needed elsewhere, it can be put in a separate module and imported wherever needed:
const connectMongoose = require("./connectMongoose");
app.use(function(req, res, next) {
connectMongoose().then(function() {
next();
});
});
require('./routes/user')(app);
require('./routes/audit')(app, io);
Note that if several parallel requests come in, only the first of these will let the global variable mongooseConnected equal a promise, and all these requests will wait for it to resolve before calling next().
Also note that additional routes of app must be registered after this app.use command, not inside it.
unless somebody comes up with a way to do this with less changes to the original code base, this is what I'm using:
cosmos.js
// pull cosmos keys
async function retriveKey(subId, resGrp, server) {
const { DefaultAzureCredential } = require("#azure/identity");
const { CosmosDBManagementClient } = require("#azure/arm-cosmosdb");
const armClient = new CosmosDBManagementClient(
new DefaultAzureCredential(), subId
);
const { primaryMasterKey } = await armClient.databaseAccounts.listKeys(
resGrp, server
);
return primaryMasterKey
}
exports.key = retriveKey
app.js
// pull cosmos keys
var cosmos = require('./cosmos');
let key = cosmos.key(var1, var2, var3)
mongoose.connect(
`xxx`,
{
auth: {
username: config.database.name,
password: key
}
}
).catch(
err => {
console.log("dOrty h4ck");
key.then(k => mongoose.connect(
`xxx`,
{
auth: {
username: config.database.name,
password: k
}
}
)
);
}
)
basically, like Heiko mentioned, mongoose.connect() is actually async, but somehow blocking (??). so while first mongoose.connect() always fails - it gives enough time for the code to retrieve the key, then I catch the error and connect again. no other changes to the original code base are needed.

sinon stub calls fake calling actual function

I'm having situation where I want to write unit test cases for a function to make sure if it is working fine or not. So I have created stub for that specific function and when I tries to calls fake that stub, the function is actually getting called instead of fake call. Below is my scenario:
I have an main function from where I'm calling the function saveData(**).
saveData(**) function is calling AWS SQS to save an message to DB
Below is my main function:
'use strict';
async function mainFunction() {
try {
await saveData(
name,
age,
);
return true;
} catch (e) {
console.log('Error - [%s]', e);
return null;
}
}
module.exports = { mainFunction };
Below is my saveData(**) function:
'use strict';
const AWS = require('aws-sdk');
const sqs = new AWS.SQS();
const saveData = async (
name,
age,
) => {
await sendMessage(JSON.stringify(dbData));
const params = {
DelaySeconds: <some_delay>,
MessageAttributes: <messageAttributes>,
MessageBody: {name:name, age:age},
QueueUrl: <URL_FOR_QUEUE>,
};
return sqs.sendMessage(params).promise();
return true;
};
module.exports = {
saveData,
};
And my test case is,
'use strict';
const express = require('express');
const bodyParser = require('body-parser');
require('app-module-path').addPath('./src');
const sinon = require('sinon');
const app = express();
const sqsSender = require('lib/queue');
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const main = require('../../../src/main-function');
const routes = require('routes');
routes.configure(app);
let mainFunctionStub;
let saveDataStub;
describe('/v1/main', () => {
beforeEach(() => {
mainFunctionStub = sinon.stub(main, 'mainFunction');
saveDataStub = sinon.stub(sqsSender, 'saveData');
});
describe('Test', () => {
it(`should return success`, (done) => {
const name = 'Name';
const age = 'Age';
saveDataStub.resolves({
name,
age,
});
});
});
afterEach(() => {
mainFunctionStub.restore();
mainFunctionStub.reset();
saveDataStub.restore();
saveDataStub.reset();
});
});
But this test is returning,
error: Jun-20-2021 20:07:05: Error - [Error [ConfigError]: Missing region in config
and,
Error: Timeout of 3500ms exceeded.
From this error I can say that this is actually calling SQS function instead of faking. How can I resolve this or how can I fake call to this function? I'm new to this unit testing so any help would be appriciated.
Stubbing works by replacing the property on the exports object. Since the require happens before sinon replaces the function, you capture the reference to the original function instead of dynamically using the currently set one.
You haven't showed your require calls in the main file, but from the call-site I infer you're importing it like const { saveData } = require('../foo/sqsSender'). This means you're grabbing the function off of the object when first loading the code. If you instead keep a reference to the sqsSender module instead, and reference the function on invocation, the stub should work.
'use strict';
// Don't destructure / grab the function.
const sqsSender = require("../foo/sqsSender")+
async function mainFunction() {
try {
// Use reference through module
await sqsSender.saveData(
name,
age,
);
return true;
} catch (e) {
console.log('Error - [%s]', e);
return null;
}
}
module.exports = { mainFunction };

How to create a async function and export it?

Using node + express. I want to create a module that use several querys.
how can I export this asynchronous function to app.js?
This is the function that im trying to make it work:
app.js (where io are socketio instance)
const users = require('./sockets/users')(io)
users.js
const Users = require('../models/Users.model')
const users = async function(client){
client.on('connection', socket =>{
socket.on('userAdd',(data) =>{
console.log(data);
})
const users = await Users.find()
console.log(users[0]);
})
}
module.exports = users
Error: SyntaxError: await is only valid in async function
First create two files. you can create functions into one and can export it and in another file you can import that functions. check the code below.
> server.js
const addition = require('./addition.js') // path to your another file
const result = addition.add(5, 8) // calling function of another file
console.log(result)
another file
> addition.js
const add = (x, y) => x + y;
module.exports = { add } // export this function
output:
13
I was typing the async keyword in the incorrect function. Its in the connection function
const Users = require('../models/Users.model')
const users = function(client){
client.on('connection', async socket =>{
socket.on('userAdd',(data) =>{
console.log(data);
})
const users = await Users.find()
console.log(users[0]); //user 1
})
}
module.exports = users
You can try to use a class for that and export that class and use it

Resources