Nodejs Asynchronous reusable function call - javascript

I have some problem developping my own Node.JS application. I'm new to this kind of code architecture and not so good with asynchronous as well.
I currently have an event in my index.js script, calling for another script database.js. This database script will handle every db call inside my application.
index.js
const db = require('database.js');
//Called when a userjoin the application, this is working well//
db.userJoinedFunction("username");
I was planning adding other function call inside my "userjoinedFunction". For example : establishing first the DB connection --> 2nd checking if the user is already inside the DB --> 3d if not, create the user entry --> 4 if yes, do welcome back message.
Since I have to establish the DB connection multiple time, I wanted to reuse some code inside theses function like this :
database.js
module.exports.userJoinedFuntion = userJoinedFunction
function userjoinedFunction(username){
Init_database_connection();
if(UserAlreadySignedup(username)){
WelcomeMessage(username);
}
else {
AddUserToDatabase(username);
}
}
but obviously i'm doing it wrong because asynchronous Node.js application can't be that simple... How can I have that type of reusable architecture ? Do you have articles I can read about the subject ?
Thank you very much for your help !
EDIT 29/05/2019
with the response of novomanish I tried this :
async fonction run(){
await(call("number 1");
await Init_database_connection();
await(call("number 2");
}
async function call(text){
console.log(text);
}
async function Init_database_connection(){
db = new sqlite3.Database("path", sqlite.OPEN_READWRITE, (err) =>{
if(err){ console.log(err); }
else { console.log("DB Connected"); }
});
}
my output are :
- number one
- number two
- DB Connected
I checked many article and documentation, I can't find a good way of reusing function inside a "main function" (like in my example : the function run()). And i'm surprised that waiting for finished DB query isn't apparently thing...

You need async/await in your functions. So the new code would look like:
async function Init_database_connection(){...}
...
async function userjoinedFunction(username){
await Init_database_connection();
if(await UserAlreadySignedup(username)){
return WelcomeMessage(username);
}
return AddUserToDatabase(username);
}
Regarding your edit, Init_database_connection should have returned a promise for await to work, like this:
async function Init_database_connection(){
return new Promise((resolve, reject)=>{
db = new sqlite3.Database("path", sqlite.OPEN_READWRITE, (err) =>{
if(err){
console.log(err);
reject();
}else {
console.log("DB Connected");
resolve()
}
});
})
}

Related

Cannot read properties of undefined (reading 'fitness')

We are currently making a website for our roommate matching project. We are at the point where we are making the results page, which should display the 3 most compatible roommates based on some questions where the algorithm calculates their compatibility percentage.
We are now trying to pass the object called "fitness" that holds all of the results, which would be:
let fit = {
fitness: Math.round(sum * 100),
email: resident[i].email,
name: resident[i].firstName + " " + resident[i].lastName,
};
To our .ejs file called results.ejs. The fit object gets pushed into our global "fitness" variable.
This is our post request:
router.post("/results", function (request, response) {
let item = request.body;
let applicant_name = request.body.firstName;
client.connect(function (err) {
const db = client.db("myFirstDatabase");
assert.equal(null, err);
db.collection("residents").insertOne(item, function (err, result) {
assert.equal(null, err);
console.log("Item inserted");
});
});
client.connect(function (err) {
const db = client.db("myFirstDatabase");
assert.equal(null, err);
db.collection("residents")
.find({})
.toArray(function (err, resident) {
if (err) throw err;
/* weighting(request); */
compatability(request, resident);
});
});
client.close();
response.render("pages/results", {
applicant_name: applicant_name,
resident: fitness,
});
});
In our results.ejs file, we use Bootstrap cards in order to display the 3 most compatible roommates. This is the code we use in the results.ejs file:
<p class="card-text">
Compatibility %: <%= resident[0].fitness %><br />
Name: <%= resident[0].name %> <br />
Email: <%= resident[0].email %><br />
Location: <br />
</p>
Whenever we run the post request, we get an error saying "Cannot read properties of undefined (reading 'fitness')", but if we refresh again, the Node.js server crashes, but it displays the actual values we want to get. If we add a "?" after "resident[0]", it runs the page without any errors but does not display any information from the object.
We have tried numerous things but cannot find the right solution to solve this issue.
Thanks in advance.
EDIT
The whole code from the .ejs file and our serverside code, with photos of how it looks on the website: https://pastebin.com/zRxdk6aq
The error "Cannot read properties of undefined (reading 'fitness')" in this case means that resident[0] is undefined. This is probably due to the fact that residentis either undefined or is an empty array (length 0).
From the code you provided I can't see, where you actually push your object into the global fitness variable or where the variable is defined. Maybe that's were the problem is. To help you further with that, it would certainly help if you could post the code for that.
Edit:
Looking at the new code you provided in your pastebin, it looks like you're using asynchronus functions but are expecting a synchronus behaviour. Your current code looks essentially like this:
router.post("/results", function (request, response) {
client.connect(function (err) {
// Insert data into db
});
client.connect(function (err) {
// Fetch data from db
});
client.close();
response.render("pages/results", {
applicant_name: applicant_name,
resident: fitness,
});
});
The problem with that is, that your program does not wait for your DB-operations to finish before rendering your page. This is because of how callback functions work.
I presume you are using the mongoDB driver for nodejs. Besides classic callback options, it also allows for the use of async/await:
router.post("/results", async (request, response) {
//
await client.connect();
const db = client.db('myFirstDatabase');
await db.collection("residents").insertOne(request.body);
let residents = await db.collection("residents").find({}).toArray();
let fitness = compatability(request, resident);
client.close();
response.render("pages/results", {
applicant_name: request.body.firstName,
resident: fitness,
});
});
function compatability(request, resident) {
let fitness = [];
// Do your calculations here
return fitness;
}
This way your request handler waits until you inserted your new item into the db and fetched the existing ones. Only after that, it renders your page and thereby ensures, that the fitness variable is already filled with data.
Note that in this example I also suggest to change your compatability function so that it returns your `fitnessĀ“ variable instead of making it global.
If you aren't familiar with async/await I recommend that you also try to learn about Promises.

Problems with MongoDB + async await

I am currently working on a little JS game (node.js, socket.io) and I need to retrieve data from my MongoDB (currently using Mongoose). Getting the data itself seems not to be the problem since I can console log it.
The problem is the timing. I need the data before the programm continues running. I've tryed using async await, but I think I might still be using it wrong since It doesn't work.
card.controller.js:
exports.CRUD = {
getCardById : async (id) => {
try {
let cards = await Card.find({number:id}).exec();
return cards ;
} catch (err) {
return 'error occured';
}
}
}
game.js:
/* Code is within a normal function (not async) */
(async () => {
game.card = await CRUD.getCardById(cardId - 1);
console.log("Game-Card: ",game.card); // Data gets logged here, but only after it already returned game
})();
/*return game*/

Asynchronous loop inside an asynchronous loop: a good idea?

I have some javascript code which does the following:
Read a .txt file and fill up an array of objects
Loop through these itens
Loop through an array of links inside each of these itens and make a request using nightmarejs
Write the result in Sql Server
My code is like this:
const Nightmare = require('nightmare');
const fs = require('fs');
const async = require('async');
const sql = require('mssql');
var links = recuperarLinks();
function recuperarLinks(){
//Read the txt file and return an array
}
const bigFunction = () => {
var aparelho = '';
async.eachSeries(links, async function (link) {
console.log('Zip Code: ' + link.zipCode);
async.eachSeries(link.links, async function(url){
console.log('URL: ' + url);
try {
await nightmare.goto(link2)
.evaluate(function () {
//return some elements
})
.end()
.then(function (result) {
//ajust the result
dadosAjustados.forEach(function (obj) {
//save the data
saveDatabase(obj, link.cep);
});
});
} catch (e) {
console.error(e);
}
}
}, function(err){
console.log('Erro: ');
console.log(err);
})
}, function (erro) {
if (erro) {
console.log('Erro: ');
console.log(erro);
}
});
}
async function salvarBanco(dados, cep){
const pool = new sql.ConnectionPool({
user: 'sa',
password: 'xxx',
server: 'xxx',
database: 'xxx'
});
pool.connect().then(function(){
const request = new sql.Request(pool);
const insert = "some insert"
request.query(insert).then(function(recordset){
console.log('Dado inserido');
pool.close();
}).catch(function(err){
console.log(err);
pool.close();
})
}).catch(function(err){
console.log(err);
});
}
lerArquivo();
It works fine, but i'm finding this async loop inside another async loop like a hack of some sort.
My outputs are something like this:
Fetching Data from cep 1
Fetching Data from url 1
Fetching Data from cep 2
Fetching Data from url 2
Fetching Data from cep 3
Fetching Data from url 3
Then it starts making the requests. Is there a better (and possibly a correct way) of doing this?
If you want to serialize your calls to nightmare.goto() and you want to simplify your code which is what you seem to be trying to do with await, then you can avoid mixing the callback-based async library with promises and accomplish your goal by only using promises like this:
async function bigFunction() {
var aparelho = '';
for (let link of links) {
for (let url of link.links) {
try {
let result = await nightmare.goto(url).evaluate(function () {
//return some elements
}).end();
//ajust the result
await Promise.all(dadosAjustados.map(obj => saveDatabase(obj, link.cep)));
} catch (e) {
// log error and continue processing
console.error(e);
}
}
}
}
Asynchronous loop inside an asynchronous loop: a good idea?
It's perfectly fine and necessary sometimes to nest loops that involve asynchronous operations. But, the loops have to be designed carefully to both work appropriately and to be clean, readable and maintainable code. Your bigFunction() does not seem to be either to me with your mix of async coding styles.
It works fine, but i'm finding this async loop inside another async loop like a hack of some sort.
If I were teaching a junior programmer or doing a code review for code from any level of developer, I would never allow code that mixes promises and the callback-based async library. It's just mixing two completely different programming styles for both control flow and error handling and all you get is a very hard to understand mess. Pick one model or the other. Don't mix. Personally, it seems to me that the future of the language is Promises for both async control flow and error propagation so that's what I would use.
Note: This appears to be pseudo-code because some references used in this code are not used or defined such as result and dadosAjustados. So, you will have to adapt this concept to your real code. For the future, we can offer you much more complete answers and often make suggested improvements that you're not even aware of if you include your real code, not an abbreviated pseudo-code.

NodeJS - Need help understanding and converting a synchronous dependant MySQL query code into something usable

this is my second Node project. I am using Node, Express, MySQL.
What I am doing is, I have an array of names of users that have posted something, I then loop over those names and for each of them I do a connection.query to get their posts and I store those into an array(after that I do some other data manipulation to it, but that's not the important part)
The problem is: my code tries to do that data manipulation before it even receives the data from the connection.query!
I google-d around and it seems async await is the thing I need, problem is, I couldn't fit it in my code properly.
// namesOfPeopleImFollowing is the array with the names
namesOfPeopleImFollowing.forEach(function(ele){
connection.query(`SELECT * FROM user${ele}posts`, function(error,resultOfThis){
if(error){
console.log("Error found" + error)
} else {
allPostsWithUsername.push([{username:ele},resultOfThis])
}
})
})
console.log(JSON.stringify(allPostsWithUsername)) // This is EMPTY, it mustn't be empty.
So, how do I convert that into something which will work properly?
(Incase you need the entire code, here it is: https://pastebin.com/dDEJbPfP though I forgot to uncomment the code)
Thank you for your time.
There are many ways to solve this. A simple one would be to wrap your function inside a promise and resolve when the callback is complete.
const allPromises = [];
namesOfPeopleImFollowing.forEach((ele) => {
const myPromise = new Promise((resolve, reject) => {
connection.query(`SELECT * FROM user${ele}posts`, (error, resultOfThis) => {
if (error) {
reject(error);
console.log(`Error found${error}`);
} else {
resolve({ username: ele });
}
});
});
allPromises.push(myPromise);
});
Promise.all(allPromises).then((result) => {
// your code here
})
You can read more about promise.all here

nodejs recursively fetch all pages from different REST end points

I am kind of struggling with my JavaScript. Still getting my head around the callbacks. I have a function which recursively fetches the next link page from an REST endpoint.which works fine for a single url
Now i need to use the same recrusive function to do same for other end points. thats where iam struggling now. It is obvious that iam missing a point or i badly understood callback concept.
please excuse the formatting What i have done so far ..
function getFullLoad(rest_url,callback){
mongoose.connect(url, function(err) {
console.log(url);
if (err) throw err;
console.log("successfully connected");
getToken(function(err, token) {
if (err) throw console.error(err);
console.log('using access token to retrieve data :', token[0]
['access_token']);
var options = {
'auth': {
'bearer': token[0]['access_token']
}
}
request.get(rest_url, options, function(error, response, body) {
if (!error && response.statusCode === 200) {
var data = JSON.parse(body);
//save data in mongo
});
}
var next_page = JSON.parse(body)["_links"]["next"]
if (next_page) {
var next_page_url = JSON.parse(body)["_links"]["next"]["href"];
console.log("Fetching data from ", next_page);
//recrusively call back with next url
getFullLoad(next_page_url,callback);
} else {
callback();
} else {
if (error) throw error;
}
});
rest_url= "http://myrest_url"
//this bit is working if there is a single url
getFullLoad(rest_url, function() {
console.log('done , got all data');
});
this works great ,now i need to call the same function with different urls . It may be some urls may not have not pagination.
so far this is my effort , it only takes the first url and then does nothing.
This bit is not working . what iam doing wrong? i tried creating another callback function. i guess my knowledge is quite limited on callbacks. any help very much appreciated
api_endpoints =[ {"url1": "http://url1"},{"url2": "http://url2"}]
api_endpoints.forEach(function(Item, endpoint) {
var endpoint_name = Object.keys(api_endpoints[endpoint]).toString());
var rest_url = api_config.BASE_URL + api_endpoints[endpoint]
[endpoint_name];
getFullLoad(rest_url, function) {
console.log('done');
});
});
Thank you for looking my question
With a bit of refactoring I think you can get something to work.
I would change your getFullLoad method to return a promise (the request.get) then in your foreach loop I would build up an array of promises that call the getFullLoad with the different urls.
Then use promises.all(arrayOfPromises) to execute them and manage the results.
It will be easier to understand and maintain long term.
Check out the promise documentation here:
Promise.all

Categories

Resources