How to properly use Parse / Promise? - javascript

I am writing some JavaScript codes using Parse.com.
To be honest, I have been reading how to use Promise and done lots of research but cannot still figure out how to use it properly..
Here is a scenario:
I have two tables (objects) called Client and InvoiceHeader
Client can have multiple InvoiceHeaders.
InvoiceHeader has a column called "Amount" and I want a total amount of each client's InvoiceHeaders.
For example, if Client A has two InvoiceHeaders with amount 30 and 20 and Client B has got nothing, the result I want to see in tempArray is '50, 0'.
However, with the following codes, it looks like it's random. I mean sometimes the tempArray got '50, 50' or "50, 0". I suspect it is due to the wrong usage of Promise.
Please help me. I have been looking into the codes and stuck for a few days.
$(document).ready(function() {
var client = Parse.Object.extend("Client");
var query = new Parse.Query(client);
var tempArray = [];
query.find().then(function(objects) {
return objects;
}).then(function (objects) {
var promises = [];
var totalForHeader = 0;
objects.forEach(function(object) {
totalForHeader = 0;
var invoiceHeader = Parse.Object.extend('InvoiceHeader');
var queryForInvoiceHeader = new Parse.Query(invoiceHeader);
queryForInvoiceHeader.equalTo('headerClient', object);
var prom = queryForInvoiceHeader.find().then(function(headers) {
headers.forEach(function(header) {
totalForHeader += totalForHeader +
parseFloat(header.get('headerOutstandingAmount'));
});
tempArray.push(totalForHeader);
});
promises.push(prom);
});
return Parse.Promise.when.apply(Parse.Promise, promises);
}).then(function () {
// after all of above jobs are done, do something here...
});
} );

Assuming Parse.com's Promise class follows the A+ spec, and I understood which bits you wanted to end up where, this ought to work:
$(document).ready(function() {
var clientClass = Parse.Object.extend("Client");
var clientQuery = new Parse.Query(clientClass);
clientQuery.find().then(function(clients) {
var totalPromises = [];
clients.forEach(function(client) {
var invoiceHeaderClass = Parse.Object.extend('InvoiceHeader');
var invoiceHeaderQuery = new Parse.Query(invoiceHeaderClass);
invoiceHeaderQuery.equalTo('headerClient', client);
var totalPromise = invoiceHeaderQuery.find().then(function(invoiceHeaders) {
var totalForHeader = 0;
invoiceHeaders.forEach(function(invoiceHeader) {
totalForHeader += parseFloat(invoiceHeader.get('headerOutstandingAmount'));
});
return totalForHeader;
});
totalPromises.push(totalPromise);
});
return Parse.Promise.when(totalPromises);
}).then(function(totals) {
// here you can use the `totals` array.
});
});

Related

JavaScript random item from an imported array behaving strangely

The following code seems to be behaving strangely. Basically, I'm importing a list of line-separated sentences from a text file, then making an array out of them. But when I try to choose a random sentence from the array, it doesn't work because sentenceString becomes undefined.
However, when I run
Math.floor(Math.random() * (sentenceArr.length) + 1);
I get a nice random number as expected.
And when I run sentenceArr.length
I get the number 12, which is indeed the length.
What am I missing?
var sentenceArr = [];
$.get('sentences.txt', function(data){
sentenceArr = data.split('\n');
});
var rand = Math.floor(Math.random() * (sentenceArr.length) + 1);
var sentenceString = sentenceArr[rand];
var sentence = sentenceString.split(' ');
Update:
I tried making a Promise as suggested below, but it still doesn't seem to be working. My new code with the Promise looks like this:
var sentenceArr = [];
var done = false;
function loadSentences() {
var rand = Math.floor(Math.random() * (sentenceArr.length) + 1);
var sentenceString = sentenceArr[rand];
var sentence = sentenceString.split(' ');
};
$.get('/sentences.txt', function(data){
sentenceArr = data.split('\n');
done = true;
});
var isItDone = new Promise(function(resolve) {
if(done) {
resolve('it worked');
}
});
//consume the promise:
var checkIfDone = function() {
isItDone
.then(function (fulfilled) {
loadSentences();
})
.catch(function (error) {
console.log('oops, it failed');
});
};
checkIfDone();
This seems to always return "oops, it failed", as if the promise is never fulfilled. However, when I check the value of "done", it is "true", meaning the Ajax request was completed before moving on to the next steps. Could anyone enlighten me? I've read three tutorials on promises already, and can't seem to figure out my error in applying the concept to my own code. Thank you.
The problem is you are trying to manipulate the file content before the response of server be complete.
Take a look at promises to understand more https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
And the way to solve your question using jquery ajax api.
var sentenceArr = [];
var file = 'path/to/file';
$.get(file)
.done(function (data) {
sentenceArr = data.split('\n');
var rand = Math.floor(Math.random() * (sentenceArr.length) + 1);
var sentenceString = sentenceArr[rand];
var sentence = sentenceString.split(' ');
console.log(sentenceArr)
console.log(rand)
console.log(sentenceString)
console.log(sentence)
});
Thanks, I solved the issue by first wrapping everything in a function, and using .then() to run everything only after the Ajax "get" was completed:
var sentenceArr = [];
const getData = () =>
$.get('http://eslquiz.net/wordmix/sentences.txt', function(data){
})
.then(data => { // use this format to run function only after json get is done (since it's async)
// "data" is the contents of the text file
sentenceArr = data.split('\n');
console.log('Made sentence array');
loadSentences();
});

Promisify google.charts.setOnLoadCallback

I've been using google's charts API and have reached a dead end. I use the API to query a spreadsheet and return some data. For visualizations I'm using Razorflow - a JS dashboard framework - not Google Charts. Getting the data is pretty straight forward using code like this (this code should work - spreadsheet is public):
function initialize() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
});
};
google.charts.setOnLoadCallback(initialize);
The above will create an array holding the column labels for column A,B and C.
Once the data is fetched I want to use the data for my charts. Problem is, I need to have the data ready before I create the charts. One way I have done this, is creating the chart before calling google.charts.setOnLoadCallback(initialize) and then populate the charts with data from inside the callback. Like this:
//create dashboard
StandaloneDashboard(function (db) {
//create chart - or in this case a KPI
var firstKPI = new KPIComponent();
//add the empty component
db.addComponent(firstKPI);
//lock the component and wait for data
firstKPI.lock();
function initializeAndPopulateChart() {
// The URL of the spreadsheet to source data from.
var myKey = "12E2fE8GWuPvXJoiRZgCZUCFhRKlW69uJAm7fch71jhA"
var query = new google.visualization.Query("https://docs.google.com/spreadsheets/d/" + myKey + "/gviz/tq?sheet=Sheet1");
query.setQuery("SELECT A,B,C WHERE A>=1 LIMIT 1");
query.send(function processResponse(response) {
var KPIData = response.getDataTable();
var KPIName = [];
myNumberOfDataColumns = KPIData.getNumberOfColumns(0) - 1;
for (var h = 0; h <= myNumberOfDataColumns ; h++) {
KPIName[h] = KPIData.getColumnLabel(h);
};
//use label for column A as header
firstKPI.setCaption(KPIName[0]);
//Set a value - this would be from the query too
firstKPI.setValue(12);
//unlock the chart
firstKPI.unlock();
});
};
google.charts.setOnLoadCallback(initializeAndPopulateChart);
});
It works but, I would like to separate the chart functions from the data loading. I guess the best solution is to create a promise. That way I could do something like this:
//create dashboard
StandaloneDashboard(function (db) {
function loadData() {
return new Promise (function (resolve,reject){
//get the data, eg. google.charts.setOnLoadCallback(initialize);
})
}
loadData().then(function () {
var firstKPI = new KPIComponent();
firstKPI.setCaption(KPIName[0]);
firstKPI.setValue(12);
db.addComponent(firstKPI);
})
});
As should be quite obvious, I do not fully understand how to use promises. The above does not work but. I have tried lots of different ways but, I do not seem to get any closer to a solution. Am I on the right track in using promises? If so, how should i go about this?
Inside a promise you need to call resolve or reject function when async job is done.
function loadData() {
return new Promise (function (resolve,reject){
query.send(function() {
//...
err ? reject(err) : resolve(someData);
});
})
}
And then you can do
loadData().then(function (someData) {
//here you can get async data
}).catch(function(err){
//here you can get an error
});
});
new Promise(resolve => {
google.charts.setOnLoadCallback(resolve);
}).then(getValues);

Not getting result from Cloud Code Parse

I have been working around this for a really long while and I think I'm sort of giving up and coming here to ask now. I have a cloud code function that first lets me get a list of categories. Every category has multiple products. This one-to-many relationship is carried out using pointers, where every product points to the category it belongs to. However I'm failing to retrieve any products.
This my code,
Parse.Cloud.define("GetCategories", function(request, response) {
var _ = require('underscore.js')
var MSTCATEGORY_CLASS = "MSTCategory";
var MSTPRODUCT_CLASS = "MSTProduct";
var CATEGORY_TAG = "Category";
var SHOP_TAG = "Shop";
var VIEWS_TAG = "Views";
var CAT_LIMIT = 10;
var PROD_LIMIT = 5;
var productList = [];
var CatAndProd = [];
var MSTCategory = Parse.Object.extend(MSTCATEGORY_CLASS);
var query = new Parse.Query(MSTCategory);
query.limit(CAT_LIMIT);
query.find().then(function(results) {
var promise = Parse.Promise.as();
_.each(results, function(result) {
promise = promise.then(function() {
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults){
ProdResults.forEach(function(product) {
productList.push(product);
});
var singleItem = {
"category" : category,
"products" : productList
};
CatAndProd.push(singleItem);
return Parse.Promise.as("Hello!");
});
});
});
return promise;
}).then(function(hello) {
var jsonObject = {
"categoryAndProducts": CatAndProd
};
response.success(jsonObject);
});
});
What I am trying to do is after getting the category, I'd fetch products in it, add them to a jsonObject. And once I'm done with all categories. I'll create an array to carry all those json objects and send it as response. This is really basic, I'm pretty sure it's become my logic is incorrect. I'm new to Javascript and Parse.
There are few errors:
in your _.each, you are overwriting the same promise object in each loop
you can return directly your singleItem via the promise resolving, not need for an extra array
you're using a global productList array so the last category will have ALL products.
Try like this, I put few comments in the code:
query.find().then(function(results) {
// an array to store all our promises
var promises = [];
results.forEach(function(result) {
// one promise for each result
var promise = new Parse.Promise();
var MSTProduct = Parse.Object.extend(MSTPRODUCT_CLASS);
var ProdQuery = new Parse.Query(MSTProduct);
ProdQuery.equalTo(CATEGORY_TAG, result);
ProdQuery.include(CATEGORY_TAG);
ProdQuery.include(SHOP_TAG);
ProdQuery.descending(VIEWS_TAG);
ProdQuery.limit(PROD_LIMIT);
var category = result;
ProdQuery.find().then(function(ProdResults) {
// remove this
// ProdResults.forEach(function(product) {
// productList.push(product);
// });
var singleItem = {
"category" : category,
"products" : ProdResults
};
// pass singleItem directly when resolving the promises
promise.resolve(singleItem);
});
promises.push(promise);
});
// return a promise resolving when ALL Promises are resolved
return Parse.Promise.when(promises);
}).then(function(allProducts) {
// allProducts is an array of singleItem
var jsonObject = {
"categoryAndProducts": allProducts
};
response.success(jsonObject);
});
Also, try to not mix Array.forEach and _.each, use one of them but not both at the same time.

Return an object when the promise finishes inside a require

When i was writing this promise to query an sql datbase i was not testing it with require i was just running the js file straight from node in the console. Now i need it to return the data when finishing the loop and I can't figure out how. Promises as well as reading data from an SQL are both new to me so i was happy to have gotten it working. But now when i require this code with
var dbData = new getDataSQL();
it returns {} instead of a nice big chunk of data.
It finishes its promises but the data is not returned.
Any ideas on how best to return the data?
module.exports = function getDataSQL(){
//JSON OBJECTS
var dates = require('./JSON/dates.js');
var companies = require('./JSON/companies.js');
//SQL FUNCTION
var sqlConJS = require('./sqlCon.js');
function fn(retVal, i, startDate, endDate){
data[i] = JSON.parse(retVal);
var total = 0;
for(var b = 0; b<Object.keys(data[i].result).length;b++){
total = total + data[i].result[b].Amount
}
data[i].totalAmount = total;
data[i].startDate = startDate;
data[i].endDate = endDate;
console.log("No= "+i,"Reccs= " + Object.keys(data[i].result).length,"StartDate=" + startDate,"EndDate=" + endDate, "Amount = " + Math.floor(total));
dataP();
}
//INIT SQL QUERY
var data = [];
var incrDat = 0;
var dataPromise = function(i){
return new Promise(function(resolve, reject){
data[i]={};
var sqlCon = new sqlConJS(fn, dates[i].startDate, dates[i].endDate, companies[9].company, i);
if(dates.length===i)reject();
else resolve();
});
};
var dataP = function(){
dataPromise(incrDat++).then().catch(function(){
console.log("done!");
console.log(data[0].result[0]["Posting Date"]);
return data;
});
}
dataP();
}
Never mind i got it, callback function offcourse! I have so much to learn still. Add cbRetData when declaring the function at the top.
module.exports = function getDataSQL(cbRetData){
under Console.log("done!"); i put
cbRetData(data);
in the main js script we create the function cbRetData like so
function cbRetData(retData){
dbData = retData;
console.log("retData to dbData coming in!");
console.log(dbData);
}
where we call the getDataSQL function after requiring it u simply pass the function cbRetData along like so.
var getdbData = new getDataSQL(cbRetData);
I'm only just beginning to come to terms with callback functions and such.

knockout push values array

when I click on button1 I get object with 50 contacts array (containing collection of arrays with phoneNumbers, Addresses...), then when I click on button 2 I get the same object but my first object is erased whereas I would like to display 50 + 50 = 100 contacts array. I tried concat method but I have some difficulties to implement.
viewModel.initializeListener = function() {
$('#button1').click(function() {
document.getElementById("button2").style.visibility = "hidden";
$('#retrievedContactsDiv').html('');
nbDisplayedContacts = 0;
console.info("test");
viewModel.ui.FlashbackReport.MoreContacts();
});
$('#button2').click(function() {
viewModel.ui.FlashbackReport.MoreContacts();
console.info("test");
});
}; `
viewModel.WeHaveMoreContacts = function(data) {
console.info("test:", data)
if (viewModel.MoreContacts) {
var newArray=ko.mapping.fromJS(data, viewModel.MoreContacts);
var concatenated = newArray.concat(dataArray);
viewModel.MoreContacts.contacts(concatenated);
} else {
viewModel.MoreContacts = ko.mapping.fromJS(data);
var dataArray = viewModel.MoreContacts.contacts();
}
I have a parameter with number of contacts to skip for the server.
function which call the server then call the mapping function :
viewModel.ui.FlashbackReport.MoreContacts()
Problem : Object # has no method 'concat'
I made a fiddle that may help you.
The first part of the function generates new contacts and the second one add them to the existing contacts.
var VM = function () {
var self = this;
self.contacts = ko.observableArray();
self.addMore = function () {
// simulate server response
var offset = self.contacts().length;
var dataFromServer = [];
for (var index = 0; index < 10; index++) {
dataFromServer.push({
name: 'contact ' + offset + index
});
}
// add each new item to existing items.
ko.utils.arrayForEach(dataFromServer, function (item) {
self.contacts.push(item);
});
};
}
Feel free to ask more explanation.
I hope it helps.

Categories