Understanding JavaScript promises in Parse - javascript

I am developing an app in Parse and I'm trying to understand promises. I'm not finding very many working examples other than the very simple ones here: https://parse.com/docs/js/guide.
I'm querying the _User table. Then I loop through the users in an _.each loop. I'm running 2 cloud functions inside the loop for each iteration. At what point do I create the promise? Do I create one for each cloud function success within the loop? Or do I push each success return value onto an array and make that the promise value outside of the loop? I've tried both but I can't figure out the correct syntax to do either, it seems.
I'll break it down in pseudo-code because that may be easier than actual code:
var query = new Parse.Query(Parse.User);
query.find().then(function(users){
loop through each user in an _.each loop and run a cloud function for each that returns a number.
If the number > 0, then I push their username onto array1.
Then I run a 2nd cloud function on the user (still within the _.each loop) that returns a number.
If the number > 0, then I push their username onto array2.
}).then(function(promisesArray){
// I would like "promisesArray" to either be the 2 arrays created in the preceding section, or a concatenation of them.
// Ultimately, I need a list of usernames here. Specifically, the users who had positive number values from the cloud functions in the preceding section
concatenate the 2 arrays, if they're not already concatenated
remove duplicates
send push notifications to the users in the array
});
Questions:
- At what point do I create & return promises & what syntax should I use for that?
- Should .then(function(promisesArray){ be .when(function(promisesArray){ (when instead of then)?

Thank you both for your ideas! This is what ultimately worked:
var query = new Parse.Query(Parse.User);
query.find().then(function(users){
var allPromises = [];
var promise1, promise2;
_.each(users, function(user){
if(user.get("myvalue") != "undefined" && user.get("myvalue") != ""){
promise1 = Parse.Cloud.run("getBatch1", {param1: param1value, param2: param2value})
.then(function(numResult){
if(Number(numResult) > 0){
return Parse.Promise.as(user.getUsername());
}
});
}
allPromises.push(promise1);
if(user.get("anothervalue")==true){
promise2 = Parse.Cloud.run("getBatch2", {param1: param1value, param2: param2value})
.then(function(numResult2){
if(Number(numResult2) > 0){
return Parse.Promise.as(user.getUsername());
}
});
}
allPromises.push(promise2);
});
// Return when all promises have succeeded.
return Parse.Promise.when(allPromises);
}).then(function(){
var allPushes = [];
_.each(arguments, function(pushUser){
// Only add the user to the push array if it's a valid user & not already there.
if(pushUser != null && allPushes.indexOf(pushUser) === -1){
allPushes.push(pushUser);
}
});
// Send pushes to users who got new leads.
if(allPushes.length > 0){
Parse.Push.send({
channels: allPushes,
data: {
alert: "You have new leads."
}
}, {
success: function () {
response.success("Leads updated and push notifications sent.");
},
error: function (error) {
console.log(error);
console.error(error);
response.error(error.message);
}
});
}
response.success(JSON.stringify(allPushes));
}, // If the query was not successful, log the error
function(error){
console.log(error);
console.error(error);
response.error(error.message);
});

I'm not familiar with Parse API but I'd do it this way. Of course, I can't test my code so tell me if it works or not:
var query = new Parse.Query(Parse.User);
query.find()
.then(function(users) {
var promises = [];
users.forEach(function(user) {
// the first API call return a promise so let's store it
var promise = cloudFn1(user)
.then(function(result) {
if (result > 0) {
// just a way to say 'ok, the promise is resolved, here's the user name'
return Parse.Promise.as(user.name);
} else {
// return another promise for that second API call
return cloudFn2(user).then(function(res) {
if (result > 0) {
return Parse.Promise.as(user.name);
}
});
}
});
// store this promise for this user
promises.push(promise);
});
// return a promise that will be resolved when all promises for all users are resolved
return Parse.Promise.when(promises);
}).then(function(myUsers) {
// remove duplicates is easy with _
myUsers = _.uniq(myUsers);
// do your push
myUsers.forEach( function(user) {
});
});

First, you need to understand what Promises are. From what I understand of what you're trying to do it should look something like this:
//constructs the Parse Object
var query = new Parse.Query(Parse.User);
//find method returns a Promise
var res = query.find()
//good names will be a Promise of an array of usernames
//whose value is above 0
var goodNames = res
.then(function(data) {
//assumes the find method returns an array of
//objects, one of the properties is username
//we will map over it to create an Array of promises
//with the eventual results of calling the AJAX fn
var numberPromises = data.map(function(obj) {
//wrap the call to the cloud function in a new
//promise
return new Promise(resolve, reject) {
someCloudFn(obj.username, function(err) {
if (err) {
reject(err);
} else {
resolve(num);
}
});
}
};
//Promise.all will take the array of promises of numbers
//and return a promise of an array of results
return [data, Promise.all(numberPromises)];
})
.then(function(arr) {
//we only get here when all of the Promises from the
//cloud function resolve
var data = arr[0];
var numbers = arr[1];
return data
.filter(function(obj, i) {
//filter out the objects whose username number
//is zero or less
return numbers[i] > 0;
})
.map(function(obj) {
//get the username out of the query result obj
return obj.username;
});
})
.catch(function(err) {
console.log(JSON.stringify(err));
});
Now whenever you need to use the list of usernames whose number isn't zero you can call the then method of goodNames and get the result:
goodNames.then(function(listOfNames) {
//do something with the names
});

Related

Waiting for a forEach to finish before return from my promise / function

I am using Firebase Cloud Firestore, however, I think this may be more of a JavaScript asynchronous vs synchronous promise return issue.
I am doing a query to get IDs from one collection, then I am looping over the results of that query to lookup individual records from another collection based on that ID.
Then I want to store each found record into an array and then return the entire array.
results.length is always 0 because return results fires before the forEach completes. If I print results.length from inside the forEach it has data.
How can I wait until the forEach is done before returning from the outer promise and the outer function itself?
getFacultyFavoritesFirebase() {
var dbRef = db.collection("users").doc(global.user_id).collection("favorites");
var dbQuery = dbRef.where("type", "==", "faculty");
var dbPromise = dbQuery.get();
var results = [];
return dbPromise.then(function(querySnapshot) {
querySnapshot.forEach(function(doc) {
var docRef = db.collection("faculty").doc(doc.id);
docRef.get().then(function(doc) {
if (doc.exists) {
results.push(doc);
}
})
});
console.log(results.length);
return results;
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
}
The trick here is to populate results with promises rather than the result. You can then call Promise.all() on that array of promises and get the results you want. Of course, you can't check if doc.exists before pushing the promise so you will need to deal with that once Promise.all() resolves. For example:
function getFacultyFavoritesFirebase() {
var dbRef = db.collection("users").doc(global.user_id).collection("favorites");
var dbQuery = dbRef.where("type", "==", "faculty");
var dbPromise = dbQuery.get();
// return the main promise
return dbPromise.then(function(querySnapshot) {
var results = [];
querySnapshot.forEach(function(doc) {
var docRef = db.collection("faculty").doc(doc.id);
// push promise from get into results
results.push(docRef.get())
});
// dbPromise.then() resolves to a single promise that resolves
// once all results have resolved
return Promise.all(results)
})
.catch(function(error) {
console.log("Error getting documents: ", error);
});
}
getFacultyFavoritesFirebase
.then(results => {
// use results array here and check for .exists
}
If you have multiple items of work to perform at the same time that come from a loop, you can collect all the promises from all the items of work, and wait for them all to finish with Promise.all(). The general form of a possible solution looks like this:
const promises = [] // collect all promises here
items.forEach(item => {
const promise = item.doWork()
promises.push(promise)
})
Promise.all(promises).then(results => {
// continue processing here
// results[0] is the result of the first promise in the promises array
})
You can adapt this to something that suits your own specific form.
Use for of instead of forEach. Like this:
for (const item of array) {
//do something
}
console.log("finished");
"finished" will be logged after finishing the loop.
Well I know, the thread is old, but the problem is still the same. And because I did run into the same issue and non of the answers did work for me, I want share my solution.
I think it will help someone out there. And maybe it will help me, if I run into the same problem again. ;-)
So the solution is super easy. firebase implements "map" but not direct on snaposhot, but on snapshot.docs.map.
In combination with Promieses.all it works just fine.
const promise = snapshot.docs.map(async (tenant) => {
return CheckTenant(tenant.id).catch(error =>
reject(error),
);
});
Promise.all(promise).then(result => {
// do somothing with the result});

Returning empty array in NodeJs using mongoose

I am trying to fill an array with records from a mongoDB database using mongoose. When I am trying to fill the records. It shows an empty array outside the function even though I am declaring the outside the function. Below is the code.
var saveMessageToVariable = function(){
var records = [];
var spark_ids = [];
var obj = new Object();
Message.find().distinct("spark_id").exec(function(err,data) {
data.forEach(function (id) {
if(id != null)
spark_ids.push(id);
});
// console.log(spark_ids.length);
spark_ids.forEach(function(spark_id){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
records.push(obj);
});
});
});
console.log(records);
}
When I run this, the log is showing an empty array. How do I resolve this issue?
It's an asynchronous call and as soon as data is fetched from database control shifts to next line and therefore prints the initial value, I would prefer you to use a callback like this:
function(spark_id,callback){
Message.findOne({"spark_id":spark_id}).sort({"date":-1}).exec(function(err,data){
obj.spark_id = data.spark_id;
obj.meesage = data.message;
obj.date = data.date;
obj.message_id = data._id;
callback(obj);
});
}
function(obj)
{
records.push(obj);
}
You two other approachs for this:
1) use try and catch block.
2) use async and await keyword.
Cheers!
I don't have much experience with moongoose, but according to the docs it supports promises since Version 4.
Then this should work:
//I assume you'll need this more often
function notNull(value){ return value != null; }
//returns a promise of the records-Array
var saveMessageToVariable = function(){
//returns a promise of a formated message
function getMessage( spark_id ){
return Message.findOne({ spark_id })
.sort({ date: -1 })
//.exec()
.then( formatMessage )
}
function formatMessage( msg ){
return {
spark_id: msg.spark_id,
message: msg.message,
date: msg.date,
message_id: msg._id
}
}
return Message.find()
.distinct("spark_id")
//.exec()
.then(function( ids ){
//waits for all findOnes to complete, then returns an Array
return Promise.all(
ids.filter( notNull ).map( getMessage )
));
}
I'm not sure, wether you need exec() in this code or not. You should check that.
//usage
saveMessageToVariable.then(function(records){
console.log(records);
})
btw. saveMessageToVariable doesn't reflect at all what this function does. You should choose a better name.

Promise nested deep map

i am trying to use Promise to make something easy, but it appear to be a real nightmare with promises. I think i am missing something with it.
I would Like to :
Fetch some articles in database
Look into each article found and iterate over article.authors Array.
Fetch each author in dataBase (including author.images) for each article
Send back to client the articles List from step One but updated with article.authors.images
I tryed severals ways using Map / Each / spread / Reduce / _.clone / _cloneDeep
But nothing works as expected
Any help would be appreciate
return Promise.bind({})
.then(function find_article(){
return Article.find().sort(req.params.sort).skip(req.params.page).limit(req.params.limit).populateAll()
}).then(function(articles){
dataBack = articles
var articlesPromise = Promise.map(articles,function(article){
console.log('------------');
var AuthorsPromise = Promise.map(article.authors,function(author){
return User.findOne(author.id).populateAll().then(function(){
})
})
return Promise.all(AuthorsPromise).then(function(data){
console.log('*******************************');
console.log(data);
return data
})
})
return Promise.all(articlesPromise).then(function(allArticles){
console.log('++++++++++++++++++++++++++++++');
console.log(allArticles);
})
})
.then(function(WhatIsInThere){
console.log('somethinAfter');
console.log(WhatIsInThere);
})
I got Something like this, but is still doesnt work i am still missing the point of the .all()
This is not trivial task, you need to chain promises and most probably use function like Promise.all() or jQuery.when()
Your code should look like this
// function that fetch from database and returns promise
function getArticleFromDatabase(articleId) {
return new Promise();
}
// function that fetch from database and returns promise
function getAuthorFromDatabase(authorId) {
return new Promise();
}
var articleIds = [1, 2, 3]; // lets have some array with article ids
// then turn it into array of promises
var articlePromises = articleIds.map(function(articleId) {
var articlePromise = getArticleFromDatabase(articleId);
// return the promise
return articlePromise.then(function(articleData) {
// here we have available complete article data
var articleAuthors = articleData.authors; // supose it's array of author ids
// lets turn it into author data promises
var authorsPromises = articleAuthors.map(function(author) {
return getAuthorFromDatabase(author.id);
});
// return new Promise, so our first promise of article data
// will return promise of article data with authors data
return Promise.all(authorsPromises)
.then(function(fullfilledAuthorsData) {
// fill in authors data
articleData.authors = fullfilledAuthorsData;
// return complete article data with authors data
return articleData;
});
});
});
Promise.all(articlePromises).then(function(fullfilledArticleData) {
// here you have available complete article data from all articles
// fullfilledActicledata is array mapped from initial array of ids
// so fullfilledActicleData[0] has data for articleIds[0],
// fullfilledActicleData[1] has data for articleIds[1] etc.
// You can use the fullfilledArticleData freely.
});
Based on your code
// this method obviously returns Promise already
var articlesPromise = Article
.find()
.sort(req.params.sort)
.skip(req.params.page)
.limit(req.params.limit)
.populateAll();
// attach callback via .then()
articlesPromise
.then(function(articles) {
// here we have fullfilled articles data already
var articlesWithAuthorsPromises = articles.map(function(article) {
var authorsPromises = article.authors.map(function(author) {
return User.findOne(author.id).populateAll();
});
return Promise.all(authorsPromises)
.then(function(fullfilledAuthors) {
article.authors = fullfilledAuthors;
return article;
})
})
// return new Promise
return Promise.all(articlesWithAuthorsPromises)
})
// attach another callback via .then()
.then(function(fullData) {
console.log(fullData);
})
// you should also listen for errors
.catch(errorCallback)

getting object Parse from a loop

In a Parse server function, it's getting Matches and profiles.
From a query to get matches another function is called to get Profiles by id but the result is :
{"_resolved":false,"_rejected":false,"_reso resolvedCallbacks":[],"_rejectedCallbacks":[]}
Main Query :
mainQuery.find().then(function(matches) {
_.each(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2')))
match.unset('profile1')
}
else if (match.get('uid2') === user.id) {
var profileMatch = _getProfile(match.get('profile1').id);
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2')
}
})
the function to get Profile info:
function _getProfile(id){
var promise = new Parse.Promise();
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.equalTo("objectId",id).find()
.then(function(result){
if(result){
promise.resolve(result);
alert("!!!!"+result);
}
else {
console.log("Profile ID: " + id + " was not found");
promise.resolve(null);
}
},
function(error){
promise.reject(error)
});
return promise;
}
Just found this a little late. You've probably moved on, but for future readers: the key to solving something like this is to use promises as returns from small, logical asynch (or sometimes asynch, as in your case) operations.
The whole _getProfile function can be restated as:
function _getProfile(id){
Parse.Cloud.useMasterKey();
var queryProfile = new Parse.Query(Profile);
return queryProfile.get(id);
}
Since it returns a promise, though, you cannot call it like this:
var myProfileObject = _getProfile("abc123");
// use result here
Instead, call it like this:
_getProfile("abc123").then(function(myProfileObject) { // use result here });
Knowing that, we need to rework the loop that calls this function. The key idea is that, since the loop sometimes produces promises, we'll need to let those promises resolve at the end.
// return a promise to change all of the passed matches' profile attributes
function updateMatchesProfiles(matches) {
// setup mainQuery
mainQuery.find().then(function(matches) {
var promises = _.map(matches, function(match) {
// Clear the current users profile, no need to return that over the network, and clean the Profile
if(match.get('uid1') === user.id) {
match.set('profile2', _processProfile(match.get('profile2'))); // assuming _processProfile is a synchronous function!!
match.unset('profile1');
return match;
} else if (match.get('uid2') === user.id) {
var profileId = match.get('profile1').id;
return _getProfile(profileId).then(function(profileMatch) {
alert(">>>"+JSON.stringify(profileMatch));
match.set('profile1', _processProfile(match.get('profile1')))
match.unset('profile2');
return match;
});
}
});
// return a promise that is fulfilled when all of the loop promises have been
return Parse.Promise.when(promises);
}

BreezeJS - Chaining Queries

Say we have a Customer Object, that has a "Foo" collection. I'd like my "getCustomer" function to add all Foos it does not have already, and then return itself, as a promise...
So I'd like a promise to: Get a Customer, then add all missing Foos to this Customer, so that when the promise is resolved, the customer has all missing foos.
Example:
// dataservice.js
// returns Q.Promise<breeze.QueryResult>
function getCustomer(custId) {
var query = breeze.EntityQuery().from("Customers").where("CustomerId", "==", custId);
return this.em.executeQuery(query);
}
// returns Q.Promise<breeze.QueryResult>
function getFoosNotOnCustomer(customer) {
var query = breeze.EntityQuery().from("Foo").where("CustomerId", "!=", customer.Id());
return this.em.executeQuery(query);
}
I am struggling with how to "chain" these together properly, what do do if no customer is found, etc. How can I modify "getCustomer" to do this? I am basically trying to user breeze synchronously. Here is my attempt, but it turns into ugly nested code in a hurry.
// want to return Q.Promise<Customer> that has Foos loaded
// I think this is actually returning something like Q.Promise<Q.Promise<Customer>>
function getCustomer(custId) {
var query = breeze.EntityQuery().from("Customers")
.where("CustomerId", "==", custId);
return this.em.executeQuery(query) // return here?
.then(function(data) {
// what about conditionals?
if(data.results.length == 1) {
getFoosNotOnCustomer(data.results[0]).
then(function (foosArray) {
$.each(foosArray, function(i,el) {
// push foos onto customer instance
}
return custWithFoos; // return here?
}
// return something?
}
}
}
Here's what I ended up doing:
function getCustomer(custId) {
var query = breeze.EntityQuery().from("Customers").where("CustomerId", "==", custId);
return manager.executeQuery(query) // return here?
.then(addFoos)
.then(doSomethingElse);
}
function addFoos(data) {
var myDefer = Q.Defer();
if (data && data.result.length == 1) {
var customer = data.results[0];
var query = // get FOOS Customer doesn't have;
manager.executeQuery(query).then(function (fooData) {
$.each(fooData.results function (i, el) {
customer.Foos.push(el);
});
myDefer.reslove(customer);
});
} else {
myDefer.resolve(undefined);
}
return myDefer.promise;
}
function doSomethingElse(customer) {
var myDefer = Q.Defer();
customer.SomePropert("test");
return myDefer.resovlve(customer);
}
// ----- MVVM
var custPromise = getCustomer(1).then(function (customer) {
// do something
});
I will take your example on face value despite my inability to understand the semantics ... in particular my inability to understand why getting all Foos not belonging to the customer is going to be helpful.
I'll just focus on "chaining" and I'll assume you want the caller to take possession of the selected customer when you're done.
Sequential chaining
In this example, we wait for the customer before getting the Foos
function getCustomer(custId) {
var cust;
var em = this.em;
var query = breeze.EntityQuery().from("Customers")
.where("CustomerId", "==", custId);
// On success calls `gotCustomer` which itself returns a promise
return em.executeQuery(query)
.then(gotCustomer)
.fail(handleFail); // you should handleFail
// Called after retrieving the customer.
// returns a new promise that the original caller will wait for.
// Defined as a nested success function
// so it can have access to the captured `cust` variable
function gotCustomer(data) {
cust = data.results[0];
if (!cust) {
return null; // no customer with that id; bail out now
}
// got a customer so look for non-customer foos
// returning another promise so caller will wait
return breeze.EntityQuery().from("Foos")
.where("CustomerId", "!=", custId)
.using(em).execute()
.then(gotFoos);
}
// Now you have both the customer and the other Foos;
// bring them together and return the customer.
function gotFoos(data) {
var foos = data.results;
// assume `notMyFoos` is an unmapped property that
// should hold every Foo that doesn't belong to this Customer
foos.forEach(function(f) { cust.notMyFoos.push(f); }
return cust; // return the customer to the caller after Foos arrive.
}
}
Parallel async queries
In your scenario you really don't have to wait for the customer query before getting the foos. You know the selection criterion for both the customer and the foos from the start. Assuming you think that there is a high probability that the customer query will return a customer, you might fire off both queries in parallel and then mash up the data when both queries complete. Consider the Q.all for this.
function getCustomer(custId) {
var em = this.em;
var custPromise = breeze.EntityQuery().from("Customers")
.where("CustomerId", "==", custId)
.using(em).execute();
var fooPromise = breeze.EntityQuery().from("Foos")
.where("CustomerId", "!=", custId)
.using(em).execute();
Q.all([custPromise, fooPromise])
.then(success)
.fail(handleFail); // you should handleFail
// Now you have both the customer and the "other" Foos;
// bring them together and return the customer.
// `data` is an array of the results from each promise in the order requested.
function success(data) {
var cust = data[0].results[0];
if (!cust) return null;
var foos = data[1].results;
// assume `notMyFoos` is an unmapped property that
// should hold every Foo that doesn't belong to this Customer
foos.forEach(function(f) { cust.notMyFoos.push(f); }
return cust; // return the customer to the caller after Foos arrive.
}
}
Notice that I don't have to do so much null checking in the success paths. I'm guaranteed to have data.results when the success callback is called. I do have to account for the possibility that there is no Customer with custId.

Categories

Resources