Better pattern for accessing nested data in Javascript - javascript

I am writing some code which iterates first through an array, and then further iterates through an array contained in the original array.
I am ending up with this weird pattern which I am repeating and I am certain is not optimized. While iterating through the last rssFeeds array item, it changes the value of 'triggerCallback' to true. Later, while iterating through the item array, a conditional checks if both triggerCallback is true and the items array is iterating through its last item, at which point it triggers a callback to be in used in async.js's waterfall pattern.
function countSuccessPosts(rssFeeds, cb){
var successCounter = 0;
var triggerCallback = ''
rssFeeds.forEach(function(feed, index, array){
if(index == array.length - 1){
triggerCallback = 'true'
}
feed.itemsToPost.forEach(function(item, itemIndex, itemArray){
if(item.response.id){
++successCounter
}
if(itemIndex == itemArray.length - 1 && triggerCallback == 'true'){
cb(null, rssFeeds, successCounter)
}
})
})
}
What's a more optimal way to structure this pattern?
Data Structure: RssFeeds will have up to 5 different itemsToPost elements.
[
{
"_id": "55808127b8f552c8157f74a7",
"name": "",
"imageUrl": "",
"url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_law.xml",
"latestDate": "1434056400000",
"endpoints": [
{
"_id": "554f9319bc479deb1757bd2e",
"name": "Wise Individ",
"id": 26413291125,
"type": "Group",
"__v": 0
}
],
"__v": 1,
"itemsToPost": [
{
"title": "Aριθμ.: Υ194/12.6.2015 Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
"summary": "Τροποποίηση απόφασης ανάθεσης αρμοδιοτήτων στον Αναπληρωτή Υπουργό Οικονομικών Δημήτριο Μάρδα.",
"url": "http://www.taxheaven.gr/laws/circular/view/id/21113",
"published_at": 1434056400000,
"time_ago": "5 days ago",
"guid": {
"link": "http://www.taxheaven.gr/laws/circular/view/id/21113",
"isPermaLink": "true"
}
}
]
},
{
"_id": "558093013106203517f96d9c",
"name": "",
"imageUrl": "",
"url": "http://www.taxheaven.gr/bibliothiki/soft/xml/soft_new.xml",
"latestDate": "1434489601236",
"endpoints": [],
"__v": 0,
"itemsToPost": [
{
"title": "Taxheaven - Άμεση ενημέρωση - Έγκαιρη επιστημονική κωδικοποίηση - Καινοτομικά εργαλεία. Κωδικοποιήθηκαν όλοι οι νόμοι στους οποίους επιφέρει αλλαγές ο νόμος 4330/2015",
"summary": {},
"url": "http://www.taxheaven.gr/news/news/view/id/24088",
"published_at": 1434494400000,
"time_ago": "about 4 hours ago",
"guid": {
"link": "http://www.taxheaven.gr/news/news/view/id/24088",
"isPermaLink": "true"
}
}
]
}
]

I didn't check this but it is pretty similar to what I'm currently using in my project:
function countSuccessPosts(rssFeeds, cb){
async.each(rssFeeds, function(eachFeed, outerCallback) {
async(eachFeed.itemToPost, function(eachItem, innerCallback) {
if(item.response.id) {
//Do Something That Is Actually Async. Could be asking the server for success flag, for instance.
doSomethingThatIsActuallyAsync(item.response.id).then(function (err) {
if (!err) {
successCounter = successCounter + 1;
}
innerCallback();
});
} else { //This could be to skip "empty responses" without the need to go to the server, right?
innerCallback();
}
}, outerCallback);
}, function() {
//All done
cb(null, rssFeeds, successCounter);
});
}
As others mentioned, you need this only if you have actual async methods calls inside the inner loop.

You don't need to keep track of the last item. Just call the callback after both loops exit. I also changed the .forEach to for loops as these execute faster.
function countSuccessPosts(rssFeeds, cb) {
var index, itemIndex, feed, item;
for (index = 0; index < rssFeeds.length; index++) {
feed = rssFeeds[index];
for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
item = feed.itemsToPost[itemIndex];
if(item.response && item.response.id) {
successCounter++;
}
}
}
cb(null, rssFeeds, successCounter);
}
Of course, if you'd rather call countSuccessPosts without a callback the calling code can look like:
var successPosts = countSuccessPosts(rssFeeds);
And you can reformat the function to look like this:
function countSuccessPosts(rssFeeds) {
var index, itemIndex, feed, item, successCounter = 0;
for (index = 0; index < rssFeeds.length; index++) {
feed = rssFeeds[index];
for (itemIndex = 0; itemIndex < feed.itemsToPost.length; itemIndex++) {
item = feed.itemsToPost[itemIndex];
if(item.response && item.response.id) {
successCounter++;
}
}
}
return successCounter;
}

Wait, why are you using a callback when you can read the data synchronously?
After you've updated your question, it looks like you're just summing a
number of elements in an array
Here's a fully synchronous version that counts the number of itemsToPost that have a valid response.id set.
function countSuccessPosts(rssFeeds) {
return rssFeeds.reduce(function(sum, x) {
return sum + x.itemsToPost.filter(function(y) {
return !!y.response.id;
}).length;
}, 0);
}
If you're required to inject this into an async control flow, you can easily put a wrapper on it
function(rssFeeds, done) {
done(null, rssFeeds, countSuccessPosts(rssFeeds));
}
The point tho, is that countSuccessPosts has a synchronous API because everything that happens within that function is synchronous.

Related

Not working javascript code - some sort of cache problem?

Array.forEach((element) => {
recipes.forEach((curr, index) => {
found = true;
item = element;
if (!curr.ingredients.includes(element)) found = false;
if (index + 1 === recipes.length && found === false) {
console.log(element, false);
} else if (index + 1 === recipes.length && found === true) {
console.log(element, true);
foundItem = element;
}
});
});
return foundItem;
Hello, guys... I am trying to debug such code... What is happening is that, as you can see in the picture down below, My code is returning me "açúcar", "calabresa", and "cerveja".
Image where you can see the output from my console
However, it is not what I mean since I am looping through a JSON list that happens to have ingredients for several different recipes...
Such as follows
"recipes": [
{
"id": "00832498-3acc-4bde-9d89-77eacef26680",
"title": "Excelente pudim de milho",
"ingredients": [
"abóbora",
"aveia",
"azeite",
"açúcar",
"cerveja",
"frango",
"milho",
"morango",
"vinho"
],
"price": 78.53
},
{
"id": "008b345d-8f7c-427d-988b-4f238b8315f2",
"title": "Excelente bolo de azeite",
"ingredients": [
"abóbora",
"aveia",
"azeite",
"açúcar",
"frango",
"vinho"
],
"price": 82.55
},
{
"id": "00d14194-f223-447e-86a0-b535c451d53f",
"title": "Exuberante coxinha de vinho",
"ingredients": [
"abóbora",
"azeite",
"açúcar",
"calabresa",
"cerveja",
"chuchu",
"frango",
"morango",
"uva",
"vinho"
],
What I want to get is açucar, since is the only ingredient which is present in all of the recipes...
As for me, it seems like there is some sort of cache-related problem here
Maybe you could change your code to this:
Array.forEach((element) => {
const isIncluded = recipes.every(recipe => recipe.ingredients.includes(element));
if (isIncluded) {
// Do something with the ingredient or the element.
}
});
return foundItem;
The every method checks that every recipe includes the ingredient that you pass to it.
if you have access to lodash you can make this really clean
import { intersection } from 'lodash'
getCommonIngredients = (recipes) => {
const ingredients = recipes.map(recipe => recipe.ingredients)
return intersection(...ingredients)
}

How to replace multiple async/await calls with Promise.all?

I have the following try/catch block which is making 3 different api calls.
The following code is working fine but it is taking lot of time to execute when firstData has large dataset.
try {
const firstData = await myservice1.myservice1Func();
for(let i=0; i<firstData.total; i++){
const hostName = firstData.rows[i]['hostname'];
if (hostName !== null && firstData.rows[i]['myservice1Id'] !== null) {
const aRes = await myService2(hostName);
firstData.rows[i]['mylist'] =
aRes[0].dataValues;
}
if (hostName !== null && firstData.rows[i]['type'].includes('type1')) {
const oRes = await myService3(hostName);
firstData.rows[i]['ores'] = oRes.rows[0];
}
if (hostName !== null && firstData.rows[i]['type'].includes('type2')) {
const vRes = await myService4(hostName);
firstData.rows[i]['vRes'] = vRes.rows[0];
}
}
return firstData;
} catch (err) {
console.log(err);
}
Here,
const firstData =
{
"total": 2,
"rows": [
{
"hostname": "abc.com",
"ipAddress": "11.11.11.11",
"myservice1Id": "ee0f77c9-ef15",
"type": "type1"
},
{
"hostname": "cde.com",
"ipAddress": "12.12.12.12",
"type": "type2",
"myservice1Id": null
}
]
}
const aRes =
[
{
"listType": "list1",
"createdAt": "2020-12-07"
}
]
const oRes =
{
"rows": [
{
"status": "FAIL"
}
]
}
const vRes =
{
"rows": [
{
"status": "FAIL"
}
]
}
The final value of firstData returned is as following:
{
"total": 2,
"rows": [
{
"hostname": "abc.com",
"ipAddress": "11.11.11.11",
"myservice1Id": "ee0f77c9-ef15",
"type": "type1",
"oRes": {
"status": "PASS"
},
"mylist": {
"listType": "list1",
"createdAt": "2020-12-07"
}
},
{
"hostname": "cde.com",
"ipAddress": "12.12.12.12",
"type": "type2",
"myservice1Id": null,
"vRes": {
"status": "FAIL"
}
}
]
}
Here, one thing to notice is that all the 3 if blocks can be executed in parallel because they are independent of each other.
Can I use Promise.all to execute all the 3 if blocks in parallel?
If yes, how the updated code will look like using Promise.all?
Simplest tweak would be to push each Promise to an array inside the ifs:
const proms = [];
if (hostName !== null && firstData.rows[i].myservice1Id !== null) {
proms.push(
myService2(hostName)
.then(aRes => firstData.rows[i].mylist = aRes[0].dataValues)
);
}
// other ifs changed around the same way
await Promise.all(proms);
You could also make the code easier by making the hostName check only once, and it looks like you're iterating over the whole array, which can be done more easily by invoking the iterator:
try {
const firstData = await myservice1.myservice1Func();
for (const row of firstData.rows) {
const hostName = row.hostname;
if (hostName === null) continue;
const proms = [];
if (row.myservice1Id !== null) {
proms.push(
myService2(hostName)
.then(aRes => row.mylist = aRes[0].dataValues)
);
}
// etc
Hi you have bit of code alterations,
for(let i=0; i<firstData.total; i++){
const hostName = firstData.rows[i]['hostname'];
//check if condition inside the service and return a null (a promise)
Promise.all([myService2(hostName), myService3(hostName), myService4(hostName)]).then((values) => {
console.log(values);
//[resutl1,null,result3]
});
}
Now the problem here is you have to wait until the slowest iteration to complete,
You can fix that with promise pool use,
#supercharge/promise-pool
MDN Promise Medium Blog Source

API Call Before Next Iteration Starts in Loop

I would like to send a POST request to a certain app through their API. What I am trying to do is to process the input data (called data) and send a POST request on one record by one record in the loop. Then, I delete the corresponding object in data for optimization purpose. I know that because of the asynchronous feature of JavaScript, the loop finishes before the function gets called. However, even though I wrap the api function in IIFE or wrap it in an async function with await(the code is below), the compiler still gives me function calls with the same parameter which is the last object. So, when I see created records on the app, David's information was generated three times. The screenshot below is each record object after being processed. If you could tell me ways of triggering the api call before the next iteration in the loop, that would be greatly appreciated.
const obj = [];
var record = {};
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
var people = data;
createKeyValue = ((key, value) => {
var temp = {};
temp["value"] = value;
obj[key] = temp;
});
apiCall = ((record) => {
clientInformation.record.addRecord.then((resp) => {
console.log(resp);
}).catch((err) => {
console.log(err);
});
});
async function asyncFunction(record) {
let promise = new Promise((resolve, reject) => {
setTimeout(() => apiCall(record), 1000)
});
let result = await promise;
console.log(result);
}
while (dataLen > 0) {
for (let [key, value] of Object.entries(data[0])) {
switch(key) {
case 'userId':
createKeyValue(key, value);
break;
case 'name':
createKeyValue(key, value);
break;
default:
}
}
record["record"] = obj;
asyncFunction(record);
data.shift();
dataLen -= 1;
}
Here is the screenshot of how each processed data looks like.
I think you haven't understand how the for loop inside the while works. The data should be incremented each time to get the next array inside data.
The data[0] => { userId: 123 ... }, data[1] => { userId: 345 ... } and so on .
At each for loop iteration checks the 3 elements of each sub array, so each time temp stores the key values for userId and name. So when the loop finishes, the temp contains as key => userId, name and the corresponding values.
var data = [
{
"userId": "123",
"name": "John",
"phoneNumber": "123-456-6789"
},
{
"userId": "345",
"name": "Summer",
"phoneNumber": "535-631-9742"
},
{
"userId" : "789",
"name": "David",
"phoneNumber": "633-753-1352"
}
]
var dataLen = data.length;
let i = 0 ;
while ( i < dataLen) {
let temp = [];
for (let [key, value] of Object.entries(data[i])) {
if(key == 'userId' || key == 'name'){
temp[key] = value;
}
}
//Just to print the values and understand
for(let k in temp){
console.log(k+" -> "+temp[k]);
}
//here you will pass the temp values to functions
console.log(" At each iteration execute the required functions ");
//asyncFunction(temp);
i += 1;
}

How to make a nested queries on mysql with Node.js?

I'm trying to make a nested queries with mysql on Node.js with Express, the result should be a json with three nested, the problem is that the return doesn't show me the third nested, my code is:
app.get('/devices/:id', ensureToken, function(req, res){
jwt.verify(req.token, jwt_secret, function(err, data) {
if (err) {
res.sendStatus(403);
} else {
var segment_id = req.param('id');
//Select devices for segment
var ls_devices = 'SELECT d.device_id AS id, d.device_name AS name, d.device_serial AS serial, d.device_description AS description, d.device_key AS keyatom, d.device_type AS type_id, dt.device_type_name AS type, d.device_user_id AS user_id, u.user_name AS username, d.device_segment_id AS segment_id, sg.segment_name, d.device_public AS isPublic, d.device_status AS status FROM ((g_devices d LEFT JOIN g_user u ON u.user_id = d.device_user_id) LEFT JOIN g_device_type dt ON dt.device_type_id = d.device_type) LEFT JOIN g_segments sg ON sg.segment_id = d.device_segment_id WHERE d.device_status = 1 AND sg.segment_id = '+segment_id;
connection.query(ls_devices, function (error, results, fields) {
if (error) throw error;
if(results.length != 0) {
var j = JSON.parse(JSON.stringify(results));
var i = 0;
var d = [];
j.forEach(function(r,index_r){
//
var ls_controllers = 'SELECT c.device_controller_id AS id, c.device_controller_name AS name, c.device_controller_description AS description, c.device_controller_devcon_type AS type_id, ct.devcon_name AS type, ct.devcon_description AS description, d.device_name AS device, d.device_id AS device_id, d.device_serial AS serial, c.device_controller_public AS public, c.device_controller_date_register AS registered, c.device_controller_date AS date, c.device_controller_status AS status FROM (g_device_controller c LEFT JOIN g_device_controller_type ct ON ct.devcon_id = c.device_controller_devcon_type) LEFT JOIN g_devices d ON d.device_id = c.device_controller_device_id WHERE c.device_controller_status = 1 AND d.device_id = '+r.id;
connection.query(ls_controllers, function (error_c, results_c, fields_c) {
var k = JSON.parse(JSON.stringify(results_c));
d.push({device:r.name,controller:[]})
k.forEach(function(r2,index_r2){
d[index_r].controller.push({name:r2.name,action:[]})
var ls_actions = 'SELECT a.action_id AS id, a.action_name AS name, a.action_description AS description, a.action_type AS type_id, aty.action_type_name, aty.action_type_description AS type_description, a.action_controller_device_id AS device_id, a.action_control AS control, a.action_value AS value, a.action_time AS time, a.action_times AS times, a.action_date AS date, a.action_status AS status FROM g_actions a LEFT JOIN g_action_type aty ON aty.action_type_id = a.action_type WHERE a.action_controller_id = '+r2.id;
connection.query(ls_actions, function (error_a, results_a, fields_a) {
if(results_a.length > 0) {
var l = JSON.parse(JSON.stringify(results_a));
l.forEach(function(r3, index_r3){
d[index_r].controller[index_r2].action.push({id_a:r3.id,name_a:r3.name});
console.log(JSON.stringify(d))
});
}
});
});
i ++;
if(i == j.length)
{
return res.json(d);
}
});
})
}
else {
return res.json({devices:false});
}
});
}
});
});
My web response is:
[
{
"device": "device one",
"controller": [
{
"name": "device controller One",
"action": []
}
]
},
{
"device": "device two",
"controller": []
}
]
And the print of the last array push is:
[
{
"device": "device one",
"controller": [
{
"name": "device controller One",
"action": [
{
"id_a": 1,
"name_a": "device action One"
}
]
}
]
},
{
"device": "device two",
"controller": [
]
}
]
k.forEach() does not wait for connection.query() to finish, so effectively the queries are skipped. The remedy is to do something similar to what you did with j.forEach(), but i++ should not happen until all of those finish too.
(Other notes: you could use promises, or async/await and make the flow appear much more neatly, or if you want to stick with learning callbacks you could use async library to simplify some of this)

Advanced Array.prototype.filter with Javascript

I have an Javascript object like so...
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
I then have an array that indicates which results I'd like back. It can be any of the following: [] OR ["war"] ["farming"] OR ["war", "farming"].
If we have the [], I want to return no results. But if ["war", "farming"] I want to return both of the results above.
How do I accomplish this with Array.prototype.filter? I saw this post, but couldn't reason through it.
strategies.filter((strategy) =>
????
)
Thanks for your help.
You can just check the value with indexOf:
var categories = ['war', 'farming'];
var filtered = strategies.filter((obj) => {
return categories.indexOf(obj.strategy.category) > -1;
});
Your object, strategy was a wrapped object, so my first line was setting it to its inner strategy and then filter as needed.
var strategies = [{
"strategy": {
"category": "war"
}
}, {
"strategy": {
"category": "farming"
}
}]
var b = ["war", "farming"];
strategies.filter(function(strategy){
strategy = strategy.strategy;
for(var i in b){
if (b[i] == strategy["category"]) {
return true;
}
}
return false;
});
Tests the input array to see if it's empty as per your requirements:
function filterObj(arr) {
return !arr.length ? arr :
strategies.filter((el) => arr.indexOf(el.strategy.category) > -1);
}
filterObj(['war', 'farming'])
DEMO

Categories

Resources