Object is once defined and once undefined in same code block - javascript

I 'm currently having a hard time figuring out why an object is once defined and on the next line it is not.
In my code, I'm declaring a promise with a for loop inside, that loops through a couple of file inputs and checks the files(images) dimension.
Once all inputs are processed the promise is resolved and the then method fires.
for(var i = 0; i < $('.apk-wrapper .mandatoryImg').length; i++){
if ($('.apk-wrapper .mandatoryImg')[i].value){
var id = $($('.apk-wrapper .mandatoryImg')[i]).attr('id');
var imageProcessing = new Promise(function(resolve,reject){
if(id == 'game_icon'){
imageProcessor($('.apk-wrapper .mandatoryImg')[i],512,512)
}
else if(id == 'ingame1'){
imageProcessor($('.apk-wrapper .mandatoryImg')[i],720,1280)
}
...
else{
error = true;
reject()
}
if(i == $('.apk-wrapper .mandatoryImg').length - 1){
resolve(images)
}
}
}).then(function(images){
console.log(images)
// console.log(images.length)
})
If I now log the array of processed images I get the correct Object with indexes.
Array[0]
0:File
1:File
2:File
3:File
4:File
5:File
length:6
When I try to get the length of that object, I tried multiple methods(.length, while hasOwnProperty, for keys in x), I always get back 0.

To get a Promise that's resolved only when all of the files are processed, try this (assuming that imageProcessor itself returns a Promise):
var promises = $('.apk-wrapper .mandatoryImg').map(function(i, el) {
var id = el.id;
if (id === 'game_icon') {
return imageProcessor(el, 512, 512);
} else if (id === 'ingame1') {
return imageProcessor(el, 720,1280)
} else if {
...
} else {
return Promise.reject();
}
}).get();
The $(...).map(...).get() chain obtains an array of Promises, one for each element in the jQuery collection.
You can then wait for all those Promises to be resolved before proceeding:
Promise.all(promises).then(function(images) {
...
});

Related

Undefined errors using Node.js, Mongoose, and Discord.js [Cannot read property of undefined]

I've been scouring similar problems but haven't seem to have found a solution that quite works on my end. So I'm working on a Discord bot that takes data from a MongoDB database and displays said data in the form of a discord embedded message using Mongoose. For the most part, everything is working fine, however one little section of my code is giving me trouble.
So I need to import an array of both all available users and the "time" data of each of those users. Here is the block of code I use to import said data:
for (i = 0;i < totalObj; i++){
timeArray[i] = await getData('time', i);
userArray[i] = await getData('user', i);
}
Now this for loop references a function I made called getData which obtains the data from MongoDB by this method:
async function getData(field, value){
var data;
await stats.find({}, function(err, result){
if(err){
result.send(err);
}else{
data = result[value];
}
});
if(field == "user"){
return data.user;
}else if (field == "time"){
return data.time;
}else{
return 0;
}
So that for loop is where my errors currently lie. When I try to run this code and display my data through a discord message, I get this error and the message does not get sent:
(node:13936) UnhandledPromiseRejectionWarning: TypeError: Cannot read property 'time' of undefined
Now the strange thing is, this error does not happen every time. If I continue calling the command that triggers this code from my discord server, it's almost like a 50/50 shot if the command actually shows the message or instead gives this error. It is very inconsistent.
This error is confounding me, as the undefined part does not make sense to me. The objects that are being searched for in the mongoDB collection are definitely defined, and the for loop never exceeds the number of objects present. My only conclusion is that I'm doing something wrong with my asynchronous function design. I have tried altering code to use the getData function less often, or to not use awaits or asynchronous design at all, however this leaves my final discord message with several undefined variables and an eventual crash.
If anyone has any advice or suggestions, that would be very much appreciated. Just for reference, here is the full function that receives the data, sorts it, and prepares a string to be displayed on the discord server (though the error only seems to occur in the first for loop):
async function buildString(){
var string = "";
var totalObj;
var timeArray = [];
var userArray = [];
var stopSort = false;
await stats.find({}, function(err, result){
if(err){
result.send(err);
}else{
totalObj = result.length;
}
});
for (i = 0;i < totalObj; i++){
timeArray[i] = await getData('time', i);
userArray[i] = await getData('user', i);
}
while(!stopSort){
var keepSorting = false;
for(i = 0; i < totalObj ; i++){
var target = await convertTime(timeArray[i]);
for(j = i + 1 ; j < totalObj ; j++){
var comparison = await convertTime(timeArray[j]);
if(target > comparison){
//Switch target time with comparison time so that the lower time is up front
var temp = timeArray[i];
timeArray[i] = timeArray[j];
timeArray[j] = temp;
//Then switch the users around so that the user always corresponds with their time
var userTemp = userArray[i];
userArray[i] = userArray[j];
userArray[j] = userTemp;
//The loop will continue if even a single switch is made
keepSorting = true;
}
}
}
if(!keepSorting){
stopSort = true;
}
}
//String building starts here
var placeArray = [':first_place: **1st', ':second_place: **2nd', ':third_place: **3rd', '**4th', '**5th', '**6th', '**7th', '**8th', '**9th', '**10th'];
for(i = 0; i < totalObj; i++){
string = await string.concat(placeArray[i] + ": " + userArray[i] + "** - " + timeArray[i] + " \n\n");
console.log('butt');
}
console.log("This String:" + string);
return string;
}
I think problem is you are trying to await function with callback, it will not work => access to data.time may run before data = result[value]. If you need await callback, you can use custom Promise (or use util.promisify, more info here)
Promise:
function findStats(options) {
return new Promise((resolve, reject) => {
return stats.find(options, function (err, result) {
if (err) {
return reject(err)
}
return resolve(result)
})
})
}
utils.promisify
const util = require('util');
const findStats = util.promisify(stats.find);
Now you can use await in your function
async function getData(field, value) {
try {
const result = await findStats({})
const data = result.value
if (field === 'user') {
return data.user
}
if (field === 'time') {
return data.time
}
return 0
} catch (error) {
// here process error the way you like
// or remove try-catch block and sanitize error in your wrap function
}
}

Async Javascript: Waiting for data to be processed in a for loop before proceeding to a new function

I'm having issues understanding how to work around Javascript's asynchronous behavior in a forEach loop. This issue is quite complex (sorry), but the idea of the loop is as followed:
Loop through every item in an array
Make an HTTP request from a provider script
I then need to multiply every element of the array by a constant
Assign the new array to an item in an object
After the loop, take all the arrays and add them together into one array
The data will be assigned to the indvCoinPortfolioChartData array
I'm looking for any flaws in my event loop. I believe the battle is making this task synchronous, making sure my data is assigned before aggregating data.
The issue
When I'm adding all the arrays together, ONE dataset isn't summed up (I think because it's still being processed after the function is called). There is no error, but it doesn't have all the coin data in the final aggregated array.
This is the issue I see in the aggregatePortfolioChartData function. It begins the for loop with only 2 items in the array, and then later shows 3. The third item was not processed until after the for loop started.
image of console log (logged from aggregatePortfolioChartData function)
debug log when aggregation is successful
var indivCoinPortfolioChartData = {'data': []};
for(var i = 0; i < this.storedCoins.Coins.length; i++)
{
let entry = this.storedCoins.Coins[i];
localThis._data.getChart(entry.symbol, true).subscribe(res => {localThis.generateCoinWatchlistGraph(res, entry);});
localThis._data.getChart(entry.symbol).subscribe(res => {
if(entry.holdings > 0)
{
let data = res['Data'].map((a) => (a.close * entry.holdings));
indivCoinPortfolioChartData.data.push({'coinData': data});
localThis.loadedCoinData(loader, indivCoinPortfolioChartData);
}
else
{
localThis.loadedCoinData(loader, indivCoinPortfolioChartData);
}
});
}
Loaded Coin Data
loadedCoinData(loader, indivCoinPortfolioChartData)
{
this.coinsWithData++;
if(this.coinsWithData === this.storedCoins.Coins.length - 1)
{
loader.dismiss();
this.aggregatePortfolioChartData(indivCoinPortfolioChartData);
}
}
aggregatePortfolioChartData
aggregatePortfolioChartData(indivCoinPortfolioChartData)
{
console.log(indivCoinPortfolioChartData);
var aggregatedPortfolioData = [];
if(indivCoinPortfolioChartData.data[0].coinData)
{
let dataProcessed = 0;
for(var i = 0; i < indivCoinPortfolioChartData.data[0].coinData.length; i++)
{
for(var j = 0; j< indivCoinPortfolioChartData.data.length; j++)
{
let data = indivCoinPortfolioChartData.data[j].coinData[i];
if(data)
{
aggregatedPortfolioData[i] = (aggregatedPortfolioData[i] ? aggregatedPortfolioData[i] : 0) + data;
dataProcessed++;
}
else
{
dataProcessed++;
}
}
if(dataProcessed === (indivCoinPortfolioChartData.data[0].coinData.length) * (indivCoinPortfolioChartData.data.length))
{
console.log(dataProcessed + " data points for portfolio chart");
this.displayPortfolioChart(aggregatedPortfolioData);
}
}
}
}
Thank you for helping me get through this irksome issue.

Resolve promise conditionally

I'm using the automated testing framework, Protractor.
I've come to find that Protractor frequently makes use of promises to asynchronously resolve code evaluation.
Question: How can I manually resolve a promise to a specific value, once a condition is met?
Update: 09/08/2017
Sorry, I was just a bit unclear on promises. I was able to get this working correctly now with:
// match variable
var match = false;
// get all elements with `div` tag
var scanElements = element.all(by.css('div')).each(function(el) {
// get text content of element
el.getText().then(function(text) {
// split words into array based on (space) delimeter
var sp = text.split(' ');
for (var i = 0; i < sp.length; i++) {
if (sp[i] == 'Stack Overflow') {
match = true;
}
}
});
});
// on complete
scanElements.then(function() {
if (match) {
console.log('Status: Found match!');
}
else {
console.log('Status: No match');
}
});
You should use map instead of each. If you look at the source code, this is how each is implemented:
each(fn: (elementFinder?: ElementFinder, index?: number) => any): wdpromise.Promise<any> {
return this.map(fn).then((): any => {
return null;
});
}
So, as you can see it internally uses map and hides the result by returning null. It is also pointed out in the documentation.
Also rename element to something else just to avoid ambiguity with protractor's element object.
There is no issue here according to the documentation the return type of element.all().each() is null after it iterates through everything.
A promise that will resolve when the function has been called on all the ElementFinders. The promise will resolve to null.
Edit 1:
Is filter a valid option?
element.all(by.css('div'))
.filter(function(element) {
return element.getText().then(function(text) {
var sp = text.split(' ');
for ( var i =0; i< sp.length; i++) {
if(sp[0] == 'protractor') return true;
}
return false;
});
}).first();
Will first filter and then return first element that matches
In your exact example you can use .getText() on ElementArrayFinder -
// calling .getText() exactly on ElementArrayFinder returns promise
let hasStackOverflow = $$('div').getText().then(texts=> {
// texts would be array of strings
return texts.includes('Your Text exact match')
})
hasStackOverflow.then(has=> {
if (has) {
console.log('Status: Found match!');
}
else {
console.log('Status: No match');
}
})

Pouchdb promise or not a promise return formatting

I have situation where I am reading a database to find out if I need to images from the images database to send to the server. In the following code, I am reading the DB_WorkIssues database and checking each record to see if there is a field called IsAttachmentInserted. If so, I take values (which are the names of the images to get from the images database) and send them to the server.
I am having a problem figuring out how to set this up.
If there are no records with the IsAttachmentInserted field, then I would do nothing. So I would assume that I would return a null. If there is an IsAttachmentInsert value, then I would return a promise that is the .get of the images database and returns a document which is the array of images.
When I am done I do a submit.
I am not sure how to handle the .then after the images promise or null return.
function GetDirtyRecords() {
var startRows
var iTemp;
var insertedImages = "";
DB_WorkIssues.allDocs({ include_docs: true, descending: false }.then(function(response) {
data = response.rows;
startRows = data.length;
iTemp = 0
for (var i = 0; i < response.total_rows; i++) {
if (data[iTemp].doc.IsDeleted){
if(data[iTemp].doc.IsAttachmentInserted)
{
if(insertedImages == "")
insertedImages += ",";
insertedImages += data[iTemp].doc.IsAttachmentInserted;
}
iTemp++;
}
}
if(insertedImages != "")
{
return DB_TaskImages.get(id, {attachments: true});
}
else
{
return ;
}
)}.then(function(doc){
// do something with the attachments
$("form").submit();
})
}
I just need to find out how to handle the last part.
Thanks

Simpy cannot iterate over javascript object?

I have scoured the other question/answer for this and implemented everything and I still cannot access the values of the object. Here's the code I am using:
function apply_voucher(voucher) {
var dates = $.parseJSON($("[name='dates']").val());
var voucher_yes_no = new Array();
var voucher_reduction = new Array();
if(voucher.length > 0)
{
$.each(dates, function(room_id, these_dates) {
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_yes_no.push('yes');
voucher_reduction.push(result.voucher_reduction);
} else {
voucher_yes_no.push('no');
}
}, 'json');
});
// check if there are any yes's in the array
if('yes' in voucher_yes_no) {
console.log("no yes's");
} else {
console.log(voucher_reduction);
console.log(typeof voucher_reduction);
for (var prop in voucher_reduction) {
console.log(prop);
console.log(voucher_reduction[prop]);
if (voucher_reduction.hasOwnProperty(prop)) {
console.log("prop: " + prop + " value: " + voucher_reduction[prop]);
}
}
}
}
}
Apologies for the constant console logging - I'm just trying to track everything to make sure it's all doing what it should. The console output I get from this is below:
...which shows the object containing one value, "1.01" and my console.log of the typeof it to make sure it is actually an object (as I thought I was going mad at one point). After this there is nothing from inside the for-in loop. I have tried jquery's $.each() also to no avail. I can't understand why nothing I'm trying is working!
It does not work because the Ajax call is asynchronous!
You are reading the values BEFORE it is populated!
Move the code in and watch it magically start working since it will run after you actually populate the Array!
function apply_voucher(voucher) {
var room_id = "169";
var dates = $.parseJSON($("[name='dates']").val());
var voucher_reduction = new Array();
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_reduction.push(result.voucher_reduction);
}
console.log(voucher_reduction);
console.log(typeof voucher_reduction);
for (var prop in voucher_reduction) {
console.log(prop);
console.log(voucher_reduction[prop]);
if (voucher_reduction.hasOwnProperty(prop)) {
console.log("prop: " + prop + " value: " + voucher_reduction[prop]);
}
}
}, 'json');
}
From what it looks like, you plan on making that Ajax call in a loop. For this you need to wait for all of the requests to be done. You need to use when() and then(). It is answered in another question: https://stackoverflow.com/a/9865124/14104
Just to say for future viewers that changing the way I did this to use proper deferred objects and promises, which blew my head up for a while, but I got there! Thanks for all the help, particularly #epascarello for pointing me in the right direction :) As soon as I started doing it this way the arrays began behaving like arrays again as well, hooray!
Here's the final code:
function apply_voucher(voucher) {
var booking_id = $("[name='booking_id']").val();
var dates = $.parseJSON($("[name='dates']").val());
if(voucher.length > 0) {
var data = []; // the ids coming back from serviceA
var deferredA = blah(data, voucher, dates); // has to add the ids to data
deferredA.done(function() { // if blah successful...
var voucher_yes_no = data[0];
var voucher_reduction = data[1];
if(voucher_yes_no.indexOf("yes") !== -1)
{
console.log("at least one yes!");
// change value of voucher_reduction field
var reduction_total = 0;
for(var i = 0; i < voucher_reduction.length; i++) {
reduction_total += voucher_reduction[i];
}
console.log(reduction_total);
}
else
{
console.log("there are no yes's");
}
});
}
}
function blah(data, voucher, dates) {
var dfd = $.Deferred();
var voucher_yes_no = new Array();
var voucher_reduction = new Array();
var cycles = 0;
var dates_length = 0;
for(var prop in dates) {
++dates_length;
}
$.each(dates, function(room_id, these_dates) {
$.post('/multiroom/check_voucher/'+voucher+'/'+room_id, function(result) {
if(result.result == 'ok') {
voucher_reduction.push(result.voucher_reduction);
voucher_yes_no.push('yes');
} else {
voucher_yes_no.push('no');
}
++cycles;
if(cycles == dates_length) {
data.push(voucher_yes_no);
data.push(voucher_reduction);
dfd.resolve();
}
}, 'json');
});
return dfd.promise();
}
Can you show how voucher_reduction is defined?
I am wondering where the second line of the debug output comes from, the one starting with '0'.
in this line:
console.log(vouncher_reduction[prop]);
^
The name of the variable is wrong (then) and probably that is breaking your code.
I think there are no problem with your loop.
But perhaps with your object.
Are you sure what properties has enumerable ?
Try to execute this to check :
Object.getOwnPropertyDescriptor(voucher_reduction,'0');
If it return undefined, the property was not exist.

Categories