I'm trying to export a variable from a file, but when I require it in another I get undefined. I suspect my functions are causing it, but I'm not sure how to fix it.
Index.js:
app.post("/", (req,res) =>{
console.log("Get from /");
module.exports.SimpleMessage = 'Hello world'; //exporting variable
exec('npx hardhat run scripts/deploy.js --network goerli',
(error, stdout, stderr) => {
if (error !== null) {
console.log(`exec error: ${error}`);
}
});
res.send("Server received the request");
});
// starting the server
app.listen(3000, () => {
console.log('listening on port 3000');
});
Deploy.js:
async function main() {
const HelloWorld = await ethers.getContractFactory("HelloWorld");
var msg = require('../src/index.js'); //requiring variable
console.log(msg.SimpleMessage);
const hello_world = await HelloWorld.deploy();
}
(Edit) This is the whole Deploy.js function:
async function main() {
const HelloWorld = await ethers.getContractFactory("HelloWorld");
var msg = require('../src/index.js'); //requiring variable
console.log(msg.SimpleMessage);
const hello_world = await HelloWorld.deploy();
}
main()
.then(() => process.exit(0))
.catch(error => {
console.error(error);
process.exit(1);
});
so there are a few things to unwrap over here:
your require() is probably invoked before module.exports.SimpleMessage = 'Hello world'; - an easy issue to encounter when you deal with async functions
require() is implemented in such a way that if you call it twice with the same file, the second time around only the cached value is returned and so, if the value of SimpleMessage changes, you will not be able to import it any more. Instead only the original value will be returned.
A quick and simple solution is to export a value that will not change instead, like a function
let SimpleMessage;
module.exports.getSimpleMessage = () => SimpleMessage;
app.post("/", (req,res) =>{
console.log("Get from /");
SimpleMessage = 'Hello world';
// and the rest of your logic
});
at this point you can reference getSimpleMessage that will be cached by require() but still will always return the updated version of SimpleMessage
Related
I am trying to take this code, split it into functions across my MVC website, and make it work with my existing database and mongoose implementation. I cloned the repo and that code all works.
So far I have adapted my database connection to this:
const connectDB = async () => {
try {
const conn = mongoose.connection;
// check connection
conn.on("error", (err) => {
console.error(`connection error: ${err.message}`);
});
conn.once("open", () => {
console.log("MongoDB Connected");
});
// init gfs
let gfs;
conn.once("open", () => {
// init stream
gfs = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: "assets",
});
});
// connection
await mongoose.connect(process.env.MONGO_URI);
return gfs;
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
I'm assuming it's okay to call mongoose.connect() multiple times as it returns a singleton, and won't create multiple connections. So I'm calling it in my controller to get access to my bucket, so I can call .find on it and get data out of it. Is there a better way?
The main issue is that my bucket exists in my controller file inside the gfs variable, since when I print it, it shows this:
[0] Promise {
[0] GridFSBucket {
[0] _events: [Object: null prototype] {},
[0] _eventsCount: 0,
[0] _maxListeners: 0,
[0] s: {
[0] db: [Db],
[0] options: [Object],
[0] _chunksCollection: [Collection],
[0] _filesCollection: [Collection],
[0] checkedIndexes: false,
[0] calledOpenUploadStream: false
[0] },
[0] [Symbol(kCapture)]: false
[0] }
[0] }
and yet when a few lines down, gfs.find() is called, an error is logged:
TypeError: gfs.find is not a function
I barely know what I'm doing, so any advice would be appreciated.
Thanks
------- update
My controller code is:
const gfs = require("../config/db").connectDB();
const uploadFile = (req, res) => {
return res.json({
message: "File uploaded successfully: " + req.file.filename,
});
};
const getMyFilenames = async (req, res) => {
console.log(gfs);
try {
const files = await gfs
.find({
//"metadata.uploader": req.user._id
})
.toArray();
if (!files || files.length === 0) {
return res.status(404).json({
message: "No files available",
});
}
const filenames = files.map((file) => file.filename.split("-")[1]);
console.log(filenames);
return res.json(filenames);
} catch (err) {
console.error(err);
}
};
u can put gfs to global
for ex
let gfs
const connectDB = async () => {
try {
const conn = mongoose.connection;
conn.on("error", (err) => {
console.error(`connection error: ${err.message}`);
});
conn.once("open", () => {
console.log("MongoDB Connected");
gfs = new mongoose.mongo.GridFSBucket(mongoose.connection.db, {
bucketName: "assets",
});
});
await mongoose.connect(process.env.MONGO_URI);
} catch (err) {
console.error(err.message);
process.exit(1);
}
};
const customF = () => {
if (!gfs) return;
console.log(gfs)
// gfs.find()
}
await connectDB()
setTimeout(()=>{
customf() // it was console.log(gfs) here
}, 10000) // i think 10s is enough to connect DB
As you saw when you printed gfs in the controller, connectDB is an async function which will return a promise, in this case resolving to the value of gfs defined in that function once it finishes executing. That TypeError is because find is not a method of a promise, so you need to await the promise first to unwrap it before calling .find.
There are multiple ways to do this, including:
Doing it inline - (await gfs).find… (and calling it something like gfsPromise could help make that clearer)
Importing the function instead, and calling it inline in place of the variable
Importing the function, and then initializing the variable in the controller with await, something like
const gfs = await connectDB();
…gfs.find(…
Not knowing the libraries, I’m not sure which you would need for your purpose around singletons and such.
Also to note, but connectDB could return/resolve the promise after mongoose.connect resolves but before the callback in your once open handler is run, and thus resolving the promise to undefined without waiting for gfs to actually be set. So you’ll likely want to actually construct the Promise to return, with the construction and resolve call lines starting something like:
const gfsPromise = new Promise(…
…resolve(new mongoose.mongo…
Hope that helps and gives you enough to go off of/make how you would want!
I am unit testing (using jest) a function named "getTripDetails" (inside file trip.js) that calls another file "getTrace.js" from different module (which exports a function as shown below).
I want to mock the call of function "getTrace" while testing "getTripDetails" function.
file: trips.js
const gpsTrace = require("./gpsTrace");
getTripDetails = async(req, res)=>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return {status:200};
}
file: getTrace.js
module.exports = async(payload, token) =>{
try {
//code
} catch (e) {
error(e)
throw new Error(e)
}
}
This is what i tried after reading the docs.
file: test.js
let ctrl = require("./trips");
describe("API -- testing", function () {
it("Trip details", async function () {
jest.mock('./gpsTrace');
const gpsTrace = require('./gpsTrace');
gpsTrace.mockImplementation(() => {});
gpsTrace();
await ctrl.getTripDetails({},{});
expect(response.status).to.eql(200);
});
});
It did not get mocked, instead it was calling the original implementation.
Any suggesstions?
You were pretty close! Here are the updated files with comments describing the changes:
gpsTrace.js
Added a console.log message. We won't see this in the test if the mock works successfully.
module.exports = async (payload, token) => {
try {
//code
console.log("You won't see me in the Jest test because of the mock implementation")
} catch (e) {
error(e)
throw new Error(e)
}
}
trips.js
You needed to export your code to be used in other modules. Seeing as you're calling ctrl.getTripDetails() in the test, it makes sense to export your getTripDetails() on an object at the bottom of the file.
const gpsTrace = require("./gpsTrace");
const getTripDetails = async (req, res) =>{
let gpsTraceRes = await gpsTrace(req.body, req.adToken)
//more code...
return { status:200 };
}
module.exports = {
getTripDetails,
}
gpsTrace.test.js
Make sure to import your modules at the top of the file. Remember that ctrl.getTripDetails({}, {}) calls gpsTrace internally, so no need to call it twice in your test. You also needed to save the response returned from getTripDetails into a variable to be able to compare it: const response = await ctrl.getTripDetails({}, {});.
// make sure your require statements go at the top of the module
const gpsTrace = require('./gpsTrace');
let ctrl = require("./trips");
jest.mock('./gpsTrace');
gpsTrace.mockImplementation(() => {});
describe("API -- testing", function () {
it("Trip details", async function () {
// ctrl.getTripDeals() calls your gpsTrace function internally, so no need to call it twice
// gpsTrace(); <-- can be removed
// you needed to save the returned response into a variable to be able to test it.
const response = await ctrl.getTripDetails({}, {});
expect(response.status).toEqual(200);
});
});
Result
After running the test it now successfully passes. Notice that we DO NOT see the console.log message in the gpsTrace function, which indicates our mockedImplementation of the function is working in the test script. 👍
First I fetch a random quote from a public api. Only AFTER I actually have a quote I want to render the page including that quote.
I am a JavaScript newbie so please bear with me if this is a stupid question.
I was struggling with waiting for the api call to return BEFORE continuing with rendering the page.
I wanted to do the following, but this doesn't work because res.render will be called before I have a quote. (note: I am using Express and Axios)
async function getRandomQuote() {
try {
const res = await axios.get("https://api.quotable.io/random?tags=famous-quotes")
console.log(`${res.data.content} - ${res.data.author}`) //This prints fine
return res.data
} catch(e) {
console.log('error', e)
}
}
app.get('/', (req, res) => {
const quote = getRandomQuote()
console.log(`${quote.content} - ${quote.author}`) //This prints 'undefined' because 'getRandomQuote' isn't finished yet
res.render('home', { quote })
})
The only way I figured out to do it is as follows, but I find this really messy.
Is there a cleaner way to do this? Or do I always need to put all the lines of code that I want to wait for each other in an async function?
async function getRandomQuote() {
try {
const res = await axios.get("https://api.quotable.io/random?tags=famous-quotes")
console.log(`${res.data.content} - ${res.data.author}`) //This prints fine
return res.data
} catch(e) {
console.log('error', e)
}
}
app.get('/', (req, res) => {
const getQuoteAndRender = async() => {
const quote = await getRandomQuote()
console.log(`${quote.content} - ${quote.author}`) //This prints only if I wrap everything in yet another aync function, otherwise it will not wait for 'getRandomQuote' to complete
res.render('home', { quote })
}
getQuoteAndRender()
})
(Note: I realize rendering the page after I successfully get a quote is not ideal either because this means I will not get a page at all if the quote api (for some reason) doesn't work. But for now I just want to know how to do this with the waiting.)
Heres what you need to do. Make your controller async by doing this app.get('/', async (req, res)
app.get('/', async (req, res) => {
const quote = await getRandomQuote()
console.log(`${quote.content} - ${quote.author}`) //This prints 'undefined' because 'getRandomQuote' isn't finished yet
res.render('home', { quote })
})
Try this:
getRandomQuote()
.then(quote => {
console.log(`${quote.content} - ${quote.author}`)
res.render('home', { quote })
});
I got two problems with this jest test:
Is it possible to define the Content collection only once instead of doing that inside of the test?
I do get this error:
Jest did not exit one second after the test run has completed.
This usually means that there are asynchronous operations that weren't stopped in your tests. Consider running Jest with --detectOpenHandles to troubleshoot this issue.
I don't see why my async code weren't stopped...
import resolvers from 'resolvers/'
import Db from 'lib/db'
const db = new Db()
describe('Resolver', () => {
let token
beforeAll(async () => {
await db.connect()
})
beforeEach(async () => {
token = 'string'
await db.dropDB()
})
afterAll(async () => {
await db.connection.close()
})
describe('articleGetContent()', () => {
test('should return dataset', async () => {
// SETUP
const Content = db.connection.collection('content')
const docs = [{
// some content...
}]
await Content.insertMany(docs)
// EXECUTE
const result = await resolvers.Query.articleGetContent({}, {
id: '123,
language: 'en'
}, {
token
})
// VERIFY
expect.assertions(1)
expect(result).toBeDefined()
})
})
})
resolver
import { articleGetContent } from '../models/article'
export default {
Query: {
articleGetContent: async (obj, { id }, { token }) => articleGetContent(id, token)
}
}
This is how my db class looks like
db.js
export default class Db {
constructor (uri, callback) {
const mongo = process.env.MONGO || 'mongodb://localhost:27017'
this.mongodb = process.env.MONGO_DB || 'testing'
this.gfs = null
this.connection = MongoClient.connect(mongo, { useNewUrlParser: true })
this.connected = false
return this
}
async connect (msg) {
if (!this.connected) {
try {
this.connection = await this.connection
this.connection = this.connection.db(this.mongodb)
this.gfs = new mongo.GridFSBucket(this.connection)
this.connected = true
} catch (err) {
console.error('mongo connection error', err)
}
}
return this
}
async disconnect () {
if (this.connected) {
try {
this.connection = await this.connection.close()
this.connected = false
} catch (err) {
console.error('mongo disconnection error', err)
}
}
}
async dropDB () {
const Content = this.connection.collection('content')
await Content.deleteMany({})
}
}
Related to the second question I hope you've found some issues on github about it.
In general, the issue is described in the debug log.
Jest works with promises, as a result, you shouldn't leave any async operations in any status except resolved.
In your case, you have your DB connection opened so you need to implement another method disconnect for your DB class, this link to docs will help you, but I guess you have it already as it's not the full db.js file ( I see some custom method dropDB. Main idea here is to have it in afterAll hook:
afterAll(() => db.disconnect());
Great example at the bottom of the page
What about the first question, it really depends on what you are doing in your method dropDB. If you're running method for dropping collection, you could store the reference to this collection somewhere outside and use it as it will automatically create the new one, but it would be great to see this method.
Additionally, your async test was created in a wrong way, you could read more here for example in my Update. You need to run this function in the beginning of the test: expect.assertions(number)
expect.assertions(number) verifies that a certain number of assertions
are called during a test. This is often useful when testing
asynchronous code, in order to make sure that assertions in a callback
actually got called.
I'm trying to build some test helper functions that will help me to run some hooks with Mocha to test my GraphQL queries(just that you understand the context).
I want to do the following steps each time before I run the tests:
Connect to mongoDB with mongoose
Start the server (node)
Add some test data directly into the database (via mongoose models)
// this function returns back the server instance because I need it in the 'after' hook to be able to stop it after the tests execution.
export const startServer = () => {
mongoose.Promise = global.Promise;
mongoose.connect(MONGO_URI_TEST);
return mongoose.connection.once('open', () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return { schema: executableSchema };
})
);
return app.listen(9000, () => {
console.log('Server started!');
});
});
};
// now here's the before hook where the server is started
let server;
before( done => {
server = startServer(done); // here I need to wait
// some other async code that inserts test data into mongo
const user = new User({email: test#test.com});
user.save().then(() => {
// saved successfully
done();
})
});
I'm quite new in the JS world, how do you manage to wait (without async await syntax) until all the promises from startServer function are resolved and you get back the server instance and just after start inserting data into the database? It's a dummy question but also some links with a better explanation of this concept that I have here would be appreciated.
LE: The entire question has reduced to the followings:
const createServer = () => {
const server = app.listen(port, () => {
//callback body here
});
};
How to convert the callback to a Promise and at the same time return a valid server reference such that in the end I can do this:
const someOtherFunction = () => {
createServer().then(myValidServerInstance => {
//do something with that instance
}
}
TLDR: Some of the things about JS Promises were not clear for me, like for example the returning of the result via resolve() method.
So the right answer looks like this:
export const startServer = () => {
mongoose.Promise = global.Promise;
// first mistake was the use of callbacks rather than Promise approach offered by mongoose.connect
return mongoose.connect(MONGO_URI_TEST).then(() => {
console.log('Connected to MongoLab TEST instance.');
return createServer();
},
err => {
console.log('Error connecting to MongoLab TEST instance:', err);
});
};
The second one was returning directly the result of listen() before the operation get finished. So I've moved the code that is starting the server in another method and wrap the result of listen() into a promise and resolve the promise only when the server actually started listening.
const createServer = () => {
const app = express();
app.post('/graphql', bodyParser.json(), graphqlExpress(req => {
return {
schema: executableSchema,
context: { headers: req.headers},
};
})
);
return new Promise((resolve, reject) => {
const server = app.listen(9000, () => {
if (server) {
console.log('Server started on port 9000');
resolve(server);
} else {
reject();
}
});
});
};