continue sub loops after error inside promise - try/catch - javascript

I'm having severe doubts that the code I'm writing is an efficient/best way to achieve my goal.
I have a promise which makes an SQL query, after it's completed I loop through an array and it's sub arrays+objects. Even if any of the subloops fail for any specific reason I want the inner loops to continue executing until the entire array has been looped through. Right now I have a "try/catch" hell which I doubt is the correct way to do this. I should however say that it works as expected, but how bad code is it?
new Promise((resolve, reject) => {
sqlConnection.execute(
'INSERT INTO pms (userId, message, conversationId) VALUES (?, ?, ?)',
[userid, receivedMsg, receivedConvId],
function(err, results) {
if (err) throw err;
resolve("DEBUG: PM from "+username+" into conv "+receivedConvId+" was sucessfully inserted to DB");
}
);
}).then(() => {
users.forEach(function(userobj, i, arr) {
try {
if (userobj.memberof.includes(receivedConvId)) {
let rcptUsername = userobj.username;
let rcptUserid = userobj.userid;
debug(rcptUsername+" is member of the group "+receivedConvId);
Object.keys(userobj.sessions).forEach(function(session) {
try {
userobj.sessions[session].forEach(function(wsConn) {
try {
debug("DEBUG: Broadcasting message to "+rcptUsername+" for connections inside session "+session);
wsConn.send(JSON.stringify(msgToSend));
} catch(err) {
errorHandler(err);
}
});
} catch(err) {
errorHandler(err);
}
});
}
} catch(err) {
errorHandler(err);
}
});
}).catch((err) => {
debug(err);
}).then(() => {
debug("INFO: Message broadcast finished");
});
The array I'm looping through could look like this:
[
{ username: 'Root',
userid: '1',
memberof: [ 1, 2, 3 ],
sessions:
{
pvkjhkjhkj21kj1hes5: [Array],
'4duihy21hkk1jhhbbu52': [Array]
}
},
{
username: 'Admin',
userid: '2',
memberof: [ 1, 2, 4 ],
sessions:
{
cg2iouoiuiou111uuok7: [Array],
sl1l3k4ljkjlkmmmmkllkl: [Array]
}
}
]
Grateful for any advice.

Assuming wsConn is a https://github.com/websockets/ws websocket - then the code you are using will only ever "detect" immediate errors anyway - any socket write failures will not be caught
You'll also be outputting "INFO: Message broadcast finished" before any of the wsConn.send have finished - because it's asynchronous
Fortunately .send has a callback, which is called back on errors or success once the send has completed - this solves both issues
Using promises is a good idea, except you haven't used promises for anything but the initial SQL execute, which is why you've ended up in nesting hell
I'm fairly confident (without your full code I can't be sure) that the following code will not only run, it has far less nesting
new Promise((resolve, reject) => {
sqlConnection.execute(
'INSERT INTO pms (userId, message, conversationId) VALUES (?, ?, ?)',
[userid, receivedMsg, receivedConvId],
(err, results) => {
if (err) {
return reject(err);
}
resolve("DEBUG: PM from " + username + " into conv " + receivedConvId + " was sucessfully inserted to DB");
}
);
}).then(() => {
const allConnectionsArray = users
.filter(({memberof}) => memberof.includes(receivedConvId)) // filter out any userobj we aren't going to send to
.map(({rcptUsername, rcptUserid, sessions}) => {
debug(rcptUsername + " is member of the group " + receivedConvId);
const userSessionsArray = Object.entries(sessions).map(([session, value]) => {
return value.map((wsConn, index) => {
return { wsConn, rcptUserid, rcptUsername, session, index };
})
});
return [].concat(...userSessionsArray); // flatten the array
});
const promises = [].concat(...allConnectionsArray) // flatten the array
.map(({ wsConn, rcptUserid, rcptUsername, session, index }) => {
debug("DEBUG: Broadcasting message to " + rcptUsername + " for connections inside session " + session);
return new Promise((resolve) => {
wsConn.send(JSON.stringify(msgToSend), err => {
if (err) {
return resolve({ rcptUserid, rcptUsername, session, index, err });
}
resolve({ rcptUserid, rcptUsername, session, index, err: false });
});
});
});
return Promise.all(promises);
}).then(results => {
/* results is an array of {
rcptUserid
rcptUsername
session
index //(index is the ordinal position in user.sessions array
err //(===false if success)
}
*/
debug("INFO: Message broadcast finished");
}).catch(error => {
// the only error caught here would be in the `return reject(err);` in the sql execute,
// because any failure in wsConn.send is a resolved promise (with an error property)
// unless I'm not seeing something obvious, they are the only possible places an error could be thrown anyway
});

Related

Nodejs Return Stop for Loop

Here is the code, and when I run it, it only executes once...
export const postStock = (body) => {
for (let val of body.order.values()) {
let sql = ` INSERT INTO stockmaster (stocknum, cat_id, user_id, dyenumber, stockQty) VALUES ('${body.stocknum}', ${JSON.stringify(val.cat_id)}, '${body.user_id}', ${JSON.stringify(val.dyenumber)}, ${JSON.stringify(val.stockQty)})`;
return sql;
};
};
So, how do I run the for loop or prevent it from stopping?
Look at how the function works, and when the loop starts, it sends the response multiple times, so I tried to set it outside of the loop, but if I set it outside of the loop, how can I send an error in response?
So now I'm stuck here...!
static stock = async (req, res) => {
for (let i = 0; i < req.body.order.length; i++) {
const body = req.body;
connection.query(postStock(body, body.order[i]), (err, result) => {
if (err) {
res.status(500).json({
code: 0,
msg: "Fail",
emsg: "Server error: " + err.message
});
} else {
connection.query(updatepStock(body.order[i]), async (err, result) => {
if (err) {
res.status(500).json({
code: 0,
msg: "Fail",
emsg: "Server error: " + err.message
});
}
})
}
})
}
res.status(201).json({code: 1,msg: "success",emsg: "Stock arrived & Product Stock updated successfully"
})
}
return stops execution and exits the function. return always exits its function immediately, with no further execution if it's inside a for loop.
So you should avoid return in case you want the loop to continue. What you can do is store the sql into array or print the data without returning anything
You can use this. return makes the loop function to exit.
export const postStock = (body) => {
let values = body.order.values();
let sql = values.map((val) => `("${body.stocknum}",
"${JSON.stringify(val.cat_id)}", "${body.user_id}",
"${JSON.stringify(val.dyenumber)}", "${JSON.stringify(val.stockQty)}")`)
let query = "INSERT INTO stockmaster (stocknum, cat_id, user_id, dyenumber,
stockQty) VALUES " + sql
}

PERN stack: Iterating through array of data, my call to my database is not going through for every data record

I have been trying like crazy to find a solution to my problem, but nothing seems to work and I don't know where I am going wrong. I am creating an app using the PERN stack, and I have an array of data with a length of 24.
I iterate through my data array with following snippet of code (this is after trying to find solutions but the result is always the same):
const createEntry = async function (data) {
let whatever = await Promise.all(
data.map(async (item) => {
try {
console.log(`${item.name}`);
await Entry.post("/", item); //call to database
} catch (err) {
console.log(err);
}
})
);
whatever.then(console.log("I hate my life."));
};
I know the entire data array is being iterated through because of the console.logs, but the call to Entry.post() is only happening like maybe six times, and I am not getting all of my data entered into my database.
My express app.post code looks like this:
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send({
status: "success",
results: results.rows.length,
data: {
entry: results.rows[0],
},
});
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
So, I resolved this on my own and found a working solution.
My createEntry code from my question:
const createEntry = async function (data) {
let whatever = await Promise.all(
data.map(async (item) => {
try {
console.log(`${item.name}`);
await Entry.post("/", item); //call to database
} catch (err) {
console.log(err);
}
})
);
whatever.then(console.log("I hate my life."));
};
now looks like this:
const createEntry = async function (data) {
try {
let result = await CreateDB.post("/", data);
return result;
} catch (err) {
console.log(err);
}
};
And my app.post code
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send({
status: "success",
results: results.rows.length,
data: {
entry: results.rows[0],
},
});
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
Now looks like this:
app.post("/url", async (req, res) => {
try {
const results = await db.query(
"INSERT INTO database (id, name) values ($1, $2)",
[
req.body.id,
req.body.name,
]
);
res.send(res.rows[0]);
} catch (err) {
console.log(`${err.detail} for ${req.body.name}`);
}
});
And my call to my createEntry is:
let temp = {obj: some object};
createEntry(temp).then((newEntry) => {
dbArray.push(newEntry.data);
manipulateData(newEntry.data);
});
And with this I am now able to create a database entry, retrieve the database object and do work with it and it works for any size array which makes me really happy. So hopefully, if anyone has a similar problem, this can help.

Post Multiple items / an array with Express & Postman

unfortunately I have problems to post an Array to my Express Server.
I post an item and if it exists in the db it will be deleted.
My code:
item.routes.js
module.exports = app => {
const item = require("../controllers/item.controller.js");
app.post("/itemr", item.IsReserved);
};
item.controller.js
const Item = require("../models/item.model.js");
exports.IsReserved = (req, res) => {
// Validate request
if (!req.body) {
res.status(400).send({
message: "Content can not be empty!"
});
}
const item = new Item({
itemId: req.body.assetId,
botId: req.body.botId,
});
Item.IsReserved(item, (err, data) => {
if (err)
res.status(500).send({
message:
err.message || "Some error occurred while creating the Item(db)."
});
else res.send(data);
});
};
item.model.js
const sql = require("./db.js");
Item.IsReserved = (RItem, result) => {
sql.query(`SELECT * FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if(res.length){
sql.query(`DELETE FROM ReservedItems WHERE itemId = "${RItem.itemId}"`, (err, res) => {
if (err) {
console.log("error: ", err);
result(err, null);
return;
}
result(null, { id: res.insertId, ...RItem });
});
}
else{
result(null, { message: "nothing to do"});
}
});
};
module.exports = Item;
A single item works great with a post like this:
{
"assetId" : 3,
"botId" : "1"
}
but if I want to insert multiple items like this:
[
{
"assetId" : 3,
"botId" : "1"
},
{
"assetId" : 4,
"botId" : "1"
}
]
The result from this is "nothing to do"...
My sql log shows: Query SELECT * FROM ReservedItems WHERE itemId = "undefined"
I saw another post here which had a similar problem, but I don't know how to implement something like ".create()" into my code to recognize multiple items.
I would be very thankful for every answer!
There are many approaches to fix your problem :)
First of all in your controller, this is not possible anymore as you send an array of items in your request payload. So you need to define an array aswell :
const items = req.body.map((item) => new Item({
itemId: item.assetId,
botId: item.botId,
});
As your method IsReserved handles only 1 item, you can use Promise.all to apply modifications. Something like this :
Promise.all( items.map( (item) => Item.isReserved(item)))
.then( (result) => ...do something here)
.catch( (error) => ...do something else here)
Another approach could be to change the SQL statement and use WHERE IN
SELECT * FROM ReservedItems WHERE itemId IN (itemid1, itemid2...)
This will get you an array of items that you can then delete the same way. It seems better in terms of performance.
I also recommand to use async/await syntax as it's way easier to read code containing promises

array push results in empty array javascript

I am trying to store API results into an array.
The data is displayed in console, but on pushing the data into an array, the array is still empty.
Here's the code:
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
var favorites = [];
dbConn.then( function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function(err, result) {
if(err) throw err;
if(!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({msg:'record not found'});
}
else {
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', {id: tweet.id}, function(err, data, response) {
if(!err){
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
}
});
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e){console.log(e)})
});
It looks like you're defining the favorites array within the scope of the function callback. Try putting var favorites = []; above you app.post() call instead.
Also, keep in mind that it will only have a value after the callback is complete, so any synchronous code later down the line will only see the empty array value.
I've updated your code to get favorites by storing separately the promise and call it afterwards:
UPDATE
As you can see in the demo, i have 2x console.log at the bottom, the first one(C1) is contained in the promise favoritesPromise () and the second (C2) is after the promise.
Synchronous actions will never wait for asynchronus actions to take place, therefore in my example C2 will always be outputted before C1, even if console.log(1 ... ) is before console.log(2 ... ), they'll appear reversed in the console.
In the promise i added a setTimeout of 1ms to mock a request, it was all it took to achieve the current output. Another thing you can test is removing the setTimeout then output will change a bit, your promise becomes synchronus until it reaches resolve(favorites), that means favorites has all the data by now, but when resolve takes place, it becomes async, and in your console you will still see C2 first (but now with data) and C1 second.
In my earlier answer i tried to implement this reasoning to your code.
Keep it async folks!
var favorites = [];
var favoritesPromise = () => {
return new Promise((resolve, reject) => {
console.log('Retrieving data from the internet.');
// This timeout mocks your request to anything that is async or promie
setTimeout(() => {
console.log('Request done')
let resultFavorite_tweets = [{
id: 1,
name: 'a dog'
}, {
id: 2,
name: 'a cat'
}];
resultFavorite_tweets.forEach(item => {
favorites.push(item.name);
})
resolve(favorites);
// if you have an error use
// reject(err)
}, 1);
});
}
favoritesPromise().then(favList => {
console.log(1, 'this will always contain data from the internet, but will always be last', favList);
})
console.log(2, 'this will be empty (unless you remove setTimeout), but will always be first', favorites);
app.post('/fetchFavoriteTweets/', verifyToken, function(req, res) {
const favoritesPromise = () => {
return new Promise((resolve, reject) => {
var favorites = [];
dbConn.then(function(database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne({
_id: ObjectId(req.userId)
}, function(err, result) {
if (err) reject(err);
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({
msg: 'record not found'
});
} else {
result.favorite_tweets.forEach(function(tweet) {
T.get('statuses/show', {
id: tweet.id
}, function(err, data, response) {
if (!err) {
favorites.push(data);
console.log(data); //this returns data
} else {
console.log(err);
reject(err);
}
});
resolve(data);
});
console.log(favorites);
// res.status(200).json({msg:'success', data:favorites});
}
});
}).catch(function(e) {
reject(e)
})
});
}
// Here you call the promise to retrieve "favorites"
favoritesPromise().then(favoritesList => {
console.log('your favorites array', favoritesList)
})
})
Try next code
app.post('/fetchFavoriteTweets/', verifyToken, function (req, res) {
var favorites = [];
dbConn.then(function (database) {
var dbo = database.db("twitter_search");
dbo.collection('users').findOne(
{ _id: ObjectId(req.userId) }, function (err, result) {
if (err) throw err;
if (!result.hasOwnProperty('favorite_tweets')) {
res.status(404).json({ msg: 'record not found' });
}
else {
// Counter
let count = result.favorite_tweets.length;
result.favorite_tweets.forEach(function (tweet) {
T.get('statuses/show', { id: tweet.id }, function (err, data, response) {
// Decrease count
count -= 1;
if (!err) {
favorites.push(data);
// Check if count is zero
if (count === 0) {
console.log(favorites);
res.status(200).json({msg:'success', data:favorites});
}
} else {
console.log(err);
}
});
});
}
});
}).catch(function (e) { console.log(e) })
});

Callback NodeJS & insert data with mongoose

I know this topic as already asked many times before but I didn't find the right answer to do what I want.
Actually, I try to save two different list of JSON object in MongoDB via Mongoose. To perform both at the same time I use 'async'.
However, when I save it with the command insertMany() I get an error because he calls the callback of async before finishing the insertMany(). Therefore answer[0] is not defined.
What will be the proper way of doing it ?
Here is my code with the async:
const mongoose = require("mongoose");
const async = require("async");
const utils = require("../utils");
const experimentCreate = function(req, res) {
let resData = {};
let experimentList = req.body.experiment;
let datasetList = req.body.datasetList;
async.parallel(
{
dataset: function(callback) {
setTimeout(function() {
answer = utils.createDataset(datasetList);
callback(answer[0], answer[1]);
}, 100);
},
experiment: function(callback) {
setTimeout(function() {
answer = utils.createExp(experimentList);
callback(answer[0], answer[1]);
}, 100);
}
},
function(err, result) {
if (err) {
console.log("Error dataset or metadata creation: " + err);
sendJSONresponse(res, 404, err);
} else {
console.log("Experiment created.");
resData.push(result.dataset);
resData.push(result.experiment);
console.log(resData);
sendJSONresponse(res, 200, resData);
}
}
);
};
Then the two functions called createExp and createDataset are the same in another file. Like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
return [err, null];
} else {
console.log("All dataset created.");
return [null, ds];
}
});
};
There's a few problems with your code. For one, you're not returning anything in your createDataset function. You're returning a value in the callback of insertMany but it doesn't return that value to the caller of createDataset as it's within another scope. To solve this issue, you can wrap your Dataset.insertMany in a promise, and resolve or reject depending on the result of Data.insertMany like this:
const createDataset = function(list) {
let datasetList = [];
for (item of list) {
let temp = {
_id: mongoose.Types.ObjectId(),
name: item.name,
description: item.description,
type: item.type,
};
datasetList.push(temp);
}
return new Promise((resolve, reject) => {
Dataset.insertMany(datasetList, (err, ds) => {
if (err) {
console.log("Error dataset creation: " + err);
reject(err);
} else {
console.log("All dataset created.");
resolve(ds);
}
});
});
};
Now your return object is no longer going to be an array so you won't be able to access both the error and the result via answer[0] and answer[1]. You're going to need to chain a then call after you call createDataset and use callback(null, answer) in the then call (as that means createDataset executed successfully) or use callback(err) if createDataset throws an error like below:
dataset: function(callback) {
setTimeout(function() {
utils.createDataset(datasetList).then(answer => {
callback(null, answer);
}).catch(err => callback(err)); // handle error here);
}, 100);
}
Note: You'll most likely need to alter your createExp code to be structurally similar to what I've produced above if it's also utilizing asynchronous functions.

Categories