I believe this may be an issue with async or promises, however, this concept has been a struggle for me to grasp. I have spent much time researching questions relating to papaparse and promises, but believe I am still not grasping some simple concept.
Overview
In the below code, a .csv file is gathered from the user, converted into an Object using PapaParse, then distance/duration values are added to the data from GoogleMaps Distance Matrix and finally, the intent is to return this data back to the user as a .csv containing this combined information.
In downloadCSV(), I am able to log the Object which results from getDistTime. However, I am unable to iterate over this object or send it to papa.unparse to convert to a .csv. Below the code, I have attached an image of the structure of arr within downloadCSV().
I have spent much time researching questions here, and on the papaparse repo. I would appreciate any help on how to best proceed.
Contents of arr:
Code
$("#submit").click(function(){
//REMOVE THIS LINE BEFORE LIVE OR VALIDATION WILL BE DUN GOOFED
event.preventDefault();
getData();
});
function getData(){
var csv = document.getElementById('exampleFormControlFile1').files[0];
// Parse CSV string
Papa.parse(csv, {
header: true,
complete: function(results) {
var final_rows = getDistTime(results)
downloadCSV(final_rows)
}
});
} // end getData
function downloadCSV(arr){
//console.log(arr) shows the results
// but trying to iterate over arr or unparsing shows undefined
console.log(arr)
csv = Papa.unparse(arr)
...more csv export code
}
function getDistTime(resources){
var rows = []
const promiseArr = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();;
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promiseArr.push(googleRequest(origin1, destinationA));
}
Promise.all(promiseArr)
.then((resultsArr) => {
resultsArr.forEach((result, i) =>
pushToRows(resources.data[i], result, rows));
})
return rows
} // end getDistTime
function pushToRows(resources, dist_dur, rows){
resources["DISTANCE_MI"] = dist_dur[0];
resources["ACTUAL_DUR_HR"] = dist_dur[1];
resources["FINANCE_DUR_HR"] = (dist_dur[0] / 45.0).toFixed(2)
rows.push(resources)
} // end pushToRows
getDistTime is performing an async action and therefore rows is returning empty before the Promise.all has resolved as you wont have pushed any data to it until that promise resolves.
Looks like you'll need to await the result of getDistTime or change how get data works, not sure you're using async await so simplest way is to just return that Promise.all and then only download the csv once its returned and resolved in your complete callback.
for example you could do
function getData(){
var csv = document.getElementById('exampleFormControlFile1').files[0];
// Parse CSV string
Papa.parse(csv, {
header: true,
complete: function(results) {
getDistTime(results).then(finalRows => downloadCSV(finalRows));
}
});
} // end getData
function getDistTime(resources){
var rows = []
const promiseArr = [];
for (var i = 0; i < resources.data.length; i++) {
var origin1 = $("#citystate").val();;
var destinationA = resources.data[i]['DEMOBILIZATION CITY'] + ',' + resources.data[i]['DEMOBILIZATION STATE'];
promiseArr.push(googleRequest(origin1, destinationA));
}
return Promise.all(promiseArr)
.then((resultsArr) => {
resultsArr.forEach((result, i) =>
pushToRows(resources.data[i], result, rows)
);
return rows
})
} // end getDistTime
Related
I have a scenario where i need to query multiple collections at once and retrieve the values based on the collection name. I use Promise.all to do so and it works accordingly like so
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("collection1").where("user_id", "==", uid).get(),
admin.firestore().collection("collection2").where("user_id", "==", uid).get(),
admin.firestore().collection("collection3").where("user_id", "==", uid).get(),
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "collection1") {
qs.docs.map((doc) => {
valuesArr1.push(doc.data().arr);
});
} else if (qs.query._queryOptions.collectionId == "Collection2") {
qs.docs.map((doc) => {
valuesArr2.push(doc.data());
});
} else if (qs.query._queryOptions.collectionId == "collection3") {
qs.docs.map((doc) => {
valuesArr3.push(doc.data());
});
}
} else {
return
}
});
for (var i=0; i < valuesArr1.length; i++) {
if (valuesArr1[i].desiredData) {
console.log('datas from for loop on datas array', valuesArr1[i].desiredData)
globalVariable += `<img src="${valuesArr1[i].desiredData}">`;
}
}
Once I do this I map the query snapshot I get and am able to retrieve the values up to this point like so
From the first collection I retrieve an array from a firestore document and then the following collections i just retrieve all documents from the collections. This all 'works' in that when I console.log into the functions console the data shows up exactly as expected. It's only when I want to iterate over the data and assign the results to a global variable to use elsewhere that strange behavior occurs.
The console.log shows the desired data in the functions console with no issues, but the output when I interpolate that data into the html and send it off in nodemailer I get the following result
undefined is always the first in the response when i use the += addition assignment operator, but if i just use the = assignment operator there's no undefined but I obviously don't get all the data I'm expecting.
There are no undefined values or documents in the collections that I'm retrieving, I've checked thoroughly and even deleted documents to make sure of it. After days of researching I've come to the conclusion it has to do with the asynchronous nature of the promise I'm working with and the data not being immediately ready when I iterate it.
Can someone help me understand what I'm doing wrong and how to fix it in node?
I figured out a solution to my problem and would like to share it in hopes it saves a future viewer some time.
Before, I was storing the results of the array from Firebase inside a global variable. To save some head scratching I'll post the code again below.
var globalVariableArray = []
var globalVariable
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "DataCollection") {
Promise.all(
qs.docs.map(doc => {
globalVariableArray = doc.data().arrayWithDesiredData;
})
);
}
else {
return
}
});
globalVariableArray.map(gv => {
globalVariable += `<p>gv.desiredData</p>` // <--- Right here is where the problem area was
})
var mailOptions = {
from: foo#blurdybloop.com,
to: 'bar#blurdybloop.com
subject: 'Almost but not quite',
html: `${globalVariable}`
};
The above code give the expected output, but the output would always have undefined first before the data showed. This happened no matter how the array from Firebase was iterated over.
After strengthening my Google-Fu, I worked out the following solution
var globalVariableArray = []
var globalVariable
var dbPromises = [];
dbPromises.push(
admin.firestore().collection("DataCollection").where("user_id", "==", uid).get()
);
const promiseConst = await Promise.all(dbPromises);
promiseConst.forEach((qs) => {
if (qs.size > 0) {
if (qs.query._queryOptions.collectionId == "DataCollection") {
Promise.all(
qs.docs.map(doc => {
globalVariableArray = doc.data().arrayWithDesiredData;
})
);
}
else {
return
}
});
var mailOptions = {
from: foo#blurdybloop.com,
to: 'bar#blurdybloop.com
subject: 'It works!!',
html: `${globalVariableArray.map(dataIWantedAllAlong => <p>dataIWantedAllAlong.desiredData</p> )}` <--- Here I simply loop through the array inside the interpolation blocks and voila! no more undefined showing up in the results
};
I perform the loop inside the brackets where I interpolate the dynamic data and am no longer getting that pesky undefined showing up in my emails.
Safe travels and happy coding to you all!
I'm a begginer in Javascript and I need to analyse a JavaScript Object generated in a loop to keep one parameter and to save this parameter for all object generated in the loop.
This is my program
var onvif = require('onvif');
var fs = require('fs');
var nombrecamera=0;
var taille=0;
var test ='';
function sleep (time) {
return new Promise((resolve) => setTimeout(resolve, time));
}
var STREAM = fs.createWriteStream('STREAM.txt',{flags:'r+'});
onvif.Discovery.on('device', function(cam,rinfo,xml){
// function will be called as soon as NVT responses
nombrecamera+=1;
console.log(cam);
test += cam;
cam2= JSON.stringify({cam}, null , ' ');
//console.log(cam2);
STREAM.write(cam2);
console.log(test);
});
onvif.Discovery.probe({timeout:1000,resolve:false});
And in output in my example i've got 4 of these:
{ probeMatches:
{ probeMatch:
{ endpointReference: [Object],
types: 'tdn:NetworkVideoTransmitter',
scopes: ' onvif://www.onvif.org/type/video_encoder onvif://www.onvif.org/location/country/china onvif://www.onvif.org/type/network_video_transmitter onvif://www.onvif.org/hardware/IPC-122 onvif://www.onvif.org/Profile/Streaming onvif://www.onvif.org/name/IPC-BO',
XAddrs: 'http://192.168.1.81:10004/onvif/device_service',
metadataVersion: 1
}
}
}
And I want to keep only the XAddrs for all object generated and then put these in a json.
My first idea was to stringify this object then create a writable stream and put all json together but in this case there are no coma between the json so it doesn't create a big json with the whole data.
Thank you for your help
Jules
The easiest way to know how many addresses you have is the .length function of an array.
As I don't know whether you need a list with unique addresses or the same address can show up multiple times, I'm gonna show you both solutions.
Unique Addresses Only
function extract() {
test.forEach(cam => {
const deviceAddress = cam.probeMatches.probeMatch.XAddrs;
// only if the xaddrs is not in list yet, add it
if(test.filter(xad => xad === deviceAddress).length <= 0) {
xaddrs.push(cam.probeMatches.probeMatch.XAddrs);
}
});
// show the number of addresses
const listCount = xaddrs.length;
console.log('listCount: ', listCount);
}
No Unique Address
function extract() {
test.forEach(cam => {
xaddrs.push(cam.probeMatches.probeMatch.XAddrs);
});
// show the number of addresses
const listCount = xaddrs.length;
console.log('listCount: ', listCount);
}
Make testan array and push()the camobjects into it. Also define an array for your XAddrs-values.
var test = [];
var xaddrs = [];
// your other code
...
onvif.Discovery.on('device', function(cam,rinfo,xml){
// function will be called as soon as NVT responses
nombrecamera+=1;
console.log(cam);
// push cam object into array
test.push(cam);
cam2= JSON.stringify({cam}, null , ' ');
//console.log(cam2);
STREAM.write(cam2);
console.log(test);
});
Then extract XAddrs and push it into xaddrs array.
function extract() {
test.forEach(cam => {
xaddrs.push(cam.probeMatches.probeMatch.XAddrs);
});
// now you have an array containing only the XAddrs elements
console.log(xaddrs);
}
I recently started development of a Node js application and it uses Selenium in a controller to fetch list of items from a web page and I want to return the fetched list of items as a JSON response.
exports.read_all_products = function (req, res) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
driver.get('https://www.test.com/products?PC=' +req.params.category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 20000, 'Could not locate the element within the time specified');
driver.findElements(By.className("product-slide-all")).then(function (elements) {
var arr = [];
elements.forEach(function (element) {
element.getAttribute("innerHTML").then(function (html) {
const dom = new JSDOM(html);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
});
res.json(arr);
});
}
Issue is I am always getting an empty JSON response even though items were added to the array. I want to know the proper way of handling this scenario.
Thanks.
It looks like the issue is because Selenium is running an async process, thus the response immediately returns because there is nothing blocking it.
findElements returns a Promise which you need to return the response from.
Take a look at How do I return the response from an asynchronous call?
Finally I was able to get it work with the help of webdriver.promise.map.
Moved web driver HTML extraction to separate function.
var findItems = function (category) {
var driver = new webdriver.Builder().forBrowser('phantomjs').build();
var map = webdriver.promise.map;
driver.get('https://www.test.com?PC=' + category);
driver.wait(until.elementLocated(By.className('product-slide-all')), 30000, 'Could not locate the element within the time specified');
var elems = driver.findElements(By.className("product-slide-all"));
return map(elems, elem => elem.getAttribute("innerHTML")).then(titles => {
return titles;
});
}
then call it from response handling function like bellow,
exports.read_all_products = function (req, res) {
findItems(req.params.category).then(function (html) {
var value;
var arr = [];
Object.keys(html).forEach(function (key) {
value = html[key];
const dom = new JSDOM(value);
var obj = new Object();
obj.product_name = dom.window.document.querySelector(".product-name").textContent;
obj.product_code = dom.window.document.querySelector(".product-code").textContent;
obj.price = dom.window.document.querySelector(".product-price").textContent;
arr.push(obj);
});
res.json(arr);
})
};
it's described in this stack overflow answers.
I need to create an array with this structure:
[
{
position: 2,
family: 9404,
part: [ 'article1', 'article2', 'article3' ]
},
{
position: 3,
family: 9405,
part: [ 'article4', 'article5', 'article6' ]
}
]
So i have a form where i select the parts that i want and send the families to get url.In the getter function i do a for to get the articles of each family and i want to query a select of articles and a select of positions. After that i try to push each array to a main array but i can't, show me undefined. How can i do this kind of operations?
I'm new with node and express and this is the first time that i have to do that.
My code:
getFamilies(req, res)
{
console.log(req.params.data);
var parsedData = JSON.parse(req.params.data);
var compounds = parsedData[0].compounds;
var supplier = parsedData[0].supplier;
var families = parsedData[0].families;
console.log(parsedData[0].compounds.length);
var position = [];
var data = [];
var parts = [];
for (var i = 0; i < compounds.length; i++)
{
parts.push(request.query("SELECT st.ref, st.design FROM st WHERE familia ='"+families[i]+"'"));
position.push(request.query("SELECT u_order FROM u_part WHERE u_familia='"+families[i]+"'"));
}
return Promise.all(parts, position, families).then(function(listOfResults)
{
//add parts, position and families to data[]
var data = [];
//console.log(data);
console.log(listOfResults);
console.log("done");
//return listOfResults;
res.render('view2', {teste: data});
}).catch(function(err)
{
// ... query error checks
console.log(err);
});
}
In promise just print the first parameter "parts" and if i put the [parts, position, families] give me promise pending.
And how can i put the data in the structure that i show above.
parseData:
[
{
"compounds": ["8"],
"supplier": ["sup"],
"families": ["9305"]
}
]
Please teach me how can i do this kind of operations.
Thank you
Not sure why you're passing families to Promise.all families seems to just be an array of data from taken from the query
Promise.all takes an array of promises in input, and you're passing an array of arrays of promises and of data...
you should never build SQL queries like this. This is a big flaw for SQL injection (but that's another question)
So do:
Promise.all([...parts, ...position]) or if you're not using ES6 syntax Promise.all(parts.concat(position))
and fix your SQL!
====
Final code could look like:
getFamilies = (req, res) => {
var families = JSON.parse(req.params.data)[0].families;
var positions = [];
var data = [];
var parts = [];
families.forEach(family => {
// see http://stackoverflow.com/a/7760578/2054629 for mysql_real_escape_string
parts.push(request.query("SELECT st.ref, st.design FROM st WHERE familia ='"+mysql_real_escape_string(family)+"'"));
positions.push(request.query("SELECT u_order FROM u_part WHERE u_familia='"+mysql_real_escape_string(family)+"'"));
});
return Promise.all([Promise.all(parts), Promise.all(positions)]).then(listOfResults => {
var [partResult, positionResult] = listOfResults;
var data = families.map((family, i) => {
var pos = positionResult[i];
var parts = partResult[i];
return {family: family, position: pos, parts: parts};
});
res.render('view2', {teste: data});
}).catch(err => {
// ... query error checks
console.log(err);
});
};
You incorrectly use Promise.all, it takes array of promises
return Promise.all([Promise.all(parts), Promise.all(position)]).then(function(listOfResults){
var partResult = listOfResults[0];
var positionResult = listOfResults[1];
var data = [];
for (var i=0; i<families.length; i++) {
var family = families[i];
var pos = positionResult[i];
var parts = partResult; // logic to extract parts for current family
data.push({family: family, position: pos, parts: parts})
}
console.log(data);
})
Promise.all() takes an array of promises, it looks like you are passing multiple arrays.
I've got a blog-post like this:
var post ="## Cool Post [embed]https://soundcloud.com/wonnemusik/teaser-schotone-dear-thomson-button-remix[/embed]"
+ "Here comes another one [embed]https://soundcloud.com/straightech-bonn-podcast/straightech-and-friends-7-oscar-ozz[/embed]";
Now for every occurrence of [embed]soundcloud-url[/embed] I need to call their API endpoint http://api.soundcloud.com/resolve.json?url=soundcloud-url&client_id=my-id, parse the returned JSON and replace the [embed] with my own markup.
How can I use this using Promises?
var post ="## Cool Post [embed]https://soundcloud.com/wonnemusik/teaser-schotone-dear-thomson-button-remix[/embed]"
+ "Here comes another one [embed]https://soundcloud.com/straightech-bonn-podcast/straightech-and-friends-7-oscar-ozz[/embed]"
var re = /\[embed\](.*)\[\/embed\]/gm;
var m;
do {
m = re.exec(post);
if (m) {
var apiCallUrl = "http://api.soundcloud.com/resolve.json?url=" + m[1] '&client_id=...'
request(apiCallUrl).then(function(body) {
var trackinfo = JSON.parse(body[0].body)
return trackinfo.title
}
}
} while (m);
// have all promises fulfilled and old embed in post-var replaced
I haven't used bluebird specifically, but there's usually an all method that wraps an array of promises (or takes promises as arguments). A glance at the API documentation for bluebird shows they do have an all method that you can use. You'll want to create an array of promises inside your do/while loop, then call all at the end:
var resultPromises = [];
var m;
do {
m = re.exec(post);
if (m) {
var apiCallUrl = "http://api.soundcloud.com/resolve.json?url=" + m[1] '&client_id=...'
resultPromises.push(request(apiCallUrl));
}
} while (m);
// have all promises fulfilled and old embed in post-var replaced
Promise.all(resultPromises).then(function (results) {
// do stuff.
});
Now, if you want to replace the original text with the results of the promise, you'll need to store the matches in an array as well. The results argument to the then will be an array of the responses, in the order in which they were originally added to the array. So, you can do something like:
var resultPromises = [];
var matches = [];
var m;
do {
m = re.exec(post);
if (m) {
var apiCallUrl = "http://api.soundcloud.com/resolve.json?url=" + m[1] '&client_id=...'
resultPromises.push(request(apiCallUrl));
matches.push(m);
}
} while (m);
var i = 0;
// have all promises fulfilled and old embed in post-var replaced
Promise.all(resultPromises).then(function (results) {
// haven't tested this. Will leave as practice for the OP :)
post = post.replace(matches[i], results[i].body[0].body.title);
i += 1;
});
You can use this replace function that deals with asynchronous callbacks:
var post = "## Cool Post [embed]https://soundcloud.com/wonnemusik/teaser-schotone-dear-thomson-button-remix[/embed]"
+ "Here comes another one [embed]https://soundcloud.com/straightech-bonn-podcast/straightech-and-friends-7-oscar-ozz[/embed]"
var re = /\[embed\](.*)\[\/embed\]/gm;
return replaceAsync(post, re, function(m, url) {
var apiCallUrl = "http://api.soundcloud.com/resolve.json?url=" + url + '&client_id=…'
return request(apiCallUrl).then(function(res) {
return JSON.parse(res[0].body).title;
});
});