Parse server each query wait until current job is done - javascript

This my code :
Parse.Cloud.job("deleteDuplicatedMates", function(request, status) {
var friendshipQuery = new Parse.Query("Friendship");
friendshipQuery.each((friendship) => {
var innerQuery1 = new Parse.Query("Friendship");
innerQuery1.equalTo("user1", friendship.get("user1"));
innerQuery1.equalTo("user2", friendship.get("user2"));
var innerQuery2 = new Parse.Query("Friendship");
innerQuery2.equalTo("user1", friendship.get("user2"));
innerQuery2.equalTo("user2", friendship.get("user1"));
var findPS = Parse.Query.or(innerQuery1, innerQuery2)
.notEqualTo("objectId", friendship.id)
.find()
.then(function(objects) {
console.log("did found");
if (objects.length > 0) {
//delete deplicated objects
Parse.Object.destroyAll(objects);
}
})
return Parse.Promise.when(findPS);
}).then(function() {
status.success("I just finished");
}, function(error) {
status.error("There was an error");
})
});
My code works fine but I need to update it so that :
The next each(friendship) is treated only if the current one has finished deleting the found object, so the flow will be like this :
get first object => find duplicated objects => delete found objects => get the second object => find duplicated objects => delete them => get the this one ...

If a promise block reaches the end of the block, and hasn't returned another promise, it automatically returns an empty resolved promise. So, I would change .find() to return findPs.find().../*rest of code there*/

Assuming
Parse.Object.destroyAll(objects);
is asynchronous, simply returning the destruction promise should do the trick.
return Parse.Object.destroyAll(objects);
It's also hard to help you for real, given how little context you've given.

Related

Problem with Promises and code timeout when inserting data into a collection

I am trying to verify if a object already exists in a wix collection and if it does cancel the inset() call to the database
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
var name = item.firstName + item.lastName;
name = name.toLowerCase();
wixData.query(context.collectionName)
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len)
for (var i = 0; i < len; i++) {
let member = members[i];
let memberName = member.firstName + member.lastName;
memberName = memberName.toLowerCase();
if (memberName === name) {
let toUpdate = {
'_id': member._id
}
wixData.update(context.collectionName, toUpdate)
return null;
}
return item;
}
});
//toHere
}
Im fairly new to and wixCode but I was expecting this to wait until the .then() is called then return as follows, but due to wixCode utilizing promises, the code goes immediately to the //toHere section of the code in which it dosent find a return and dismisses the call. Which adds the data to the database instead of returning null.
OK so when we look at your code it seems that you want to find a matching record to the one you are trying to insert to Memberships and then abort the insert and execute an update if one exists. The better way to do this is to look for the specific record match using the query .eq() function. If this finds a matching record then you can update otherwise continue with the insert. See below.
So quickly what is a promise?
In layman's terms think of a Promise like a FedEx tracking code.
When you call a promise based function to ask it to do something you are given back a Promise that what you asked for will be done or that you will be told if a problem occurred.
Just like a fed ex tracking code - you don't know if something has arrived (your function has done what you want it to) unless you check the tracking status using the code OR the fedex delivery van arrives and you receive the package. At which point the tracking code is updated to say the package was delivered and you might get a text or email saying it has been delivered.
A Promise function either completes successfully and you get a result in the then function call OR if an error occurs a catch() event is triggered. So All Promise based functions are similar to if then else conditional test only they look like this:
sendFedEx(package) // Call doesn't resolve immediately you have to wait for fedEx!
.then((fedExDeliveryInfo) => {
//Your package arrived!
})
.catch((fedExLostPackageInfo) => {
//Your package got lost :-(
});
In your code you do a case insensitive compare outside of the data collection. This is probably going to get resource intensive with a large data collection. A better way would be to store the case insensitive string:
(item.firstName + item.lastName).toLowerCase() in the data record and then use it for your query using .eq. That way you let the data collection do its job and simplify your code.
Note: This makes use of the fact that beforeInsert() returns a Promise.
Syntax:
function beforeInsert(item: Object, context: HookContext): Promise
Here is a modified suggestion for you with lots of comments!
import wixData from "wix-data";
export function Memberships_beforeInsert(item, context) {
// Memberships_beforeInsert returns a promise. So does
// wixData.query...find() so we simply return it to maintain the Promise
// chain.
var compareName = (item.firstName + item.lastName).toLowerCase();
// Add a compareName column value to item for future querying
// This will be inserted into the data collection
item.compareName = compareName;
//-------------------------------------------------------//
// This is the head of the Promise chain we need to return
//-------------------------------------------------------//
return wixData.query(context.collectionName)
.eq('compareName', item.compareName) // Query the compareName
.find()
.then((res) => {
var members = res.items;
var len = res.length;
console.log(len);
// Should only have one record or no records otherwise we have a
// problem with this code :-)
// So if we have too many we throw and error. This will be caught in
// an outer catch if we have one
if (len > 1) {
throw Error(`Internal Error! Too many records for ${item.firstName} ${item.lastName}`);
}
// If we get here we have a valid record OR we need to return a result
// To do this we will use a return variable set to the item which
// assumes we will insert item. This will be overridden with the save
// Promise if we have a record already
var result = item;
if (len === 1) {
// We have a record already so we need to update it and return null
// to the caller of beforeInsert to halt the insert. This is a
// Simple case of adding the _id of the found record to the item we
// have been given.
item['_id'] = member._id;
// Again remember we are using promises so we need to return the
// wixData.update result which is a promise.
result = wixData.update(context.collectionName, toUpdate)
.then((savedRecord) => {
// Now we have chained the update to the beforeInsert response
// This is where we can tell the Insert function to abort by
// returning null.
return null;
});
}
// Now we can return the result we have determined to the caller
return result;
});
}
This should do what you are trying to accomplish.

javascript - return value when event triggers within Meteor.method

I am trying to use a node-image-scraper package to scrape some images from a webpage, then return the array of images to the client in Meteor. However, I currently have no idea how this would work given the event driven nature of the pack.
var imageScraper = new imagescraper();
var images;
Meteor.methods({
scrapeImgs(url){
imageScraper.on('image', (image) => {
images.push(image);
});
images = [];
imageScraper.address = url;
imageScraper.scrape();
imageScraper.on('end', () => {
return images; //does not work
});
return images; // returns an empty array
},
});
Returning images from the event does not work, and returning images outside returns an empty array. Is there any way to make this work? Or is it fundamentally not possible given the circumstances? How would this be done if I need to return the full set of images to the client?
Simply return a promise:
Meteor.methods({
scrapeImgs(url){
let images = []; // define vars
imageScraper.on('image', (image) => {
images.push(image);
});
imageScraper.address = url;
imageScraper.scrape();
return new Promise((resolve, reject) => {
imageScraper.on('end', () => {
resolve(images); // This will only trigger on end
// Check end really triggers when expected
// Don't use return on callbacks unless terminating explicitely
});
// Guessing it has an error event...
imageScraper.on('error', (err) => {
reject(new Meteor.Error(err));
// Reject with Meteor.Error since these are caught and sanitized
})
});
// returns an empty array as array was populated on event callback, not on current process loop.
},
});
Depending on your front end call, you can either access the value directly or access .then(), just check that on front end :)

Cloud Functions for Firebase - Error serializing return value:

I have a Cloud Function used to cross reference two lists and find values that match each other across the lists. The function seems to be working properly, however in the logs I keep seeing this Error serializing return value: TypeError: Converting circular structure to JSON . Here is the function...
exports.crossReferenceContacts = functions.database.ref('/cross-ref-contacts/{userId}').onWrite(event => {
if (event.data.previous.exists()) {
return null;
}
const userContacts = event.data.val();
const completionRef = event.data.adminRef.root.child('completed-cross-ref').child(userId);
const removalRef = event.data.ref;
var contactsVerifiedOnDatabase ={};
var matchedContacts= {};
var verifiedNumsRef = event.data.adminRef.root.child('verified-phone-numbers');
return verifiedNumsRef.once('value', function(snapshot) {
contactsVerifiedOnDatabase = snapshot.val();
for (key in userContacts) {
//checks if a value for this key exists in `contactsVerifiedOnDatabase`
//if key dioes exist then add the key:value pair to matchedContacts
};
removalRef.set(null); //remove the data at the node that triggered this onWrite function
completionRef.set(matchedContacts); //write the new data to the completion-node
});
});
I tried putting return in front of completionRef.set(matchedContacts); but that still gives me the error. Not sure what I am doing wrong and how to rid the error. Thanks for your help
I was having the exact same issue when returning multiple promises that were transactions on the Firebase database. At first I was calling:
return Promise.all(promises);
My promises object is an array that I'm using where I'm pushing all jobs that need to be executed by calling promises.push(<add job here>). I guess that this is an effective way of executing the jobs since now the jobs will run in parallel.
The cloud function worked but I was getting the exact same error you describe.
But, as Michael Bleigh suggested on his comment, adding then fixed the issue and I am no longer seeing that error:
return Promise.all(promises).then(() => {
return true;
}).catch(er => {
console.error('...', er);
});
If that doesn't fix your issue, maybe you need to convert your circular object to a JSON format. An example is written here, but I haven't tried that: https://stackoverflow.com/a/42950571/658323 (it's using the circular-json library).
UPDATE December 2017: It appears that in the newest Cloud Functions version, a cloud function will expect a return value (either a Promise or a value), so return; will cause the following error: Function returned undefined, expected Promise or value although the function will be executed. Therefore when you don't return a promise and you want the cloud function to finish, you can return a random value, e.g. return true;
Try:
return verifiedNumsRef.once('value').then(function(snapshot) {
contactsVerifiedOnDatabase = snapshot.val();
for (key in userContacts) {
//checks if a value for this key exists in `contactsVerifiedOnDatabase`
//if key dioes exist then add the key:value pair to matchedContacts
};
return Promise.all([
removalRef.set(null), //remove the data at the node that triggered this onWrite function
completionRef.set(matchedContacts)
]).then(_ => true);
});
I had the same error output with a pretty similar setup and couldn't figure out how to get rid of this error.
I'm not totally sure if every essence has been captured by the previous answers so I'm leaving you my solution, maybe it helps you.
Originally my code looked like this:
return emergencyNotificationInformation.once('value', (data) => {
...
return;
});
But after adding then and catch the error did go away.
return emergencyNotificationInformation.once('value')
.then((data) => {
...
return;
})
.catch((error) => {
...
return:
});
}
We fixed a similar issue with the same error by returning Promise.resolve() at the bottom of the chain, e.g.:
return event.data.ref.parent.child('subject').once('value')
.then(snapshot => {
console.log(snapshot.val());
Promise.resolve();
}).catch(error => {
console.error(error.toString());
});

How to hold my response in nodejs until the end of my loop

My code is as follows :
myinsts.forEach(function (myinstId) {
Organization.getOrgById(myinstId,function (err,insts)
{
res.json(insts);
})
});
I'm usng Node.js and I'm getting the error "Can't set headers after they are sent" , Obviously my server sends first iteration , how can I make it hold until I get the whole data
It's not going to be pretty. Essentially what you could do is create a variable to hold the data through every iteration and then check if that's the last callback to be called. If it is, then you can output your json. Try something like the following:
var _counter = 0;
var _values = [];
myinsts.forEach(function (myinstId) {
Organization.getOrgById(myinstId,function (err,insts)
{
_values.push(insts);
if(++_counter == myinsts.length)
res.json(_values);
})
});
You would like to use Promises for this kind of works, you can execute each async function in parallel with Promise.all() and get the data in the order that is called.
var promises = [];
myinsts.forEach(function (myinstId) {
promises.push(new Promise((resolve, reject)=>{
Organization.getOrgById(myinstId,function (err,insts){
if(err) return reject(err);
resolve(insts);
});
}));
});
Promise.all(promises)
.then((allInsts)=>{res.json(allInsts)}) // all data fetched in loop order.
.catch((error)=>{console.log(error)});
Also consider to make your getOrgById handler return a Promise instead of using callback.
You have to send response just one time, not several times. Your code send the response each of iteration. Send the response once if specific condition fulfilled. Like below.
myinsts.forEach(function (myinstId, index) {
Organization.getOrgById(myinstId,function (err,insts){
if( index == myinsts.length -1 ){
res.json(insts);
}
})
});

Promises with angularJS and Typescript

I have the following function, my goal is to push to the items list, when the components can identify their parent item.
The problem that I have is that when I am pushing to the list console.log() shows me that the object is in there, but when I return the list and catch it in another function, there is nothing in the list.
I think that the items list is returned before the code above it is done.
private get_items_for_request() {
return this.Item.forRequest(this.request.id, ['group'])
.then((_items) => {
var items = [];
for (var item of _items) {
return this.ItemComponent.forItem(item.id, ['type'])
.then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
items.push({
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
});
break;
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}
return items;
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
});
}
I'm afraid the trouble is with your approach, not with the code itself. A promise is just that - a promise of data some time in future. So, when you function returns (immediately), the promise is not resolved yet and the data your caller function captures is still empty. I don't see console.log() in your code, but would suspect you put it somewhere inside then(), which will be called once data has been received. So you will see data being logged. Trouble is by that time get_items_for_request() has already returned and your caller function has already moved on.
If you want to use a promise, you have to use a callback to be called once promise is resolved. But if you want actual data returned to the caller you need to fetch data synchronously.
For the synchronous fetch, check this response. But beware that synchronous fetch will hurt responsiveness of your script.
For an asynchronous fetch (using promises), you need to define a callback called once all data has been fetched. I won't try to fix your code, but it should go along the following sketch in javascript. Though keep in mind it's just a sketch.
function get_items_for_request(onSuccess) {
var items = []
var total = -1
var sofar = 0;
this.Item.forRequest(this.request.id, ['group'])
.then(function (_items) {
var items = [];
total = _items.length // remember how many nested calls will be made
for (var item of _items) {
this.ItemComponent.forItem(item.id, ['type'])
.then(function (_components) {
// push received data to the items array here
sofar++
if (sofar == total) { // all done
onSuccess(items)
}
}
}
}
}
In the first promise callback, you have two returns, only the first one runs. You return a promise after the first for, which you resolve to undefined. You should wait for an array of promises, each corresponding to a this.ItemComponent.forItem call.
Promise.all helps with this: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all
You should do something like this:
return this.Item.forRequest(this.request.id, ['group']).then((_items) => {
return Promise.all(_items.map(function (item) {
return this.ItemComponent.forItem(item.id, ['type']).then((_components) => {
for (var component of _components) {
if (component.type.can_identify_item) {
return {
group_id: item.group.reference,
identifier_code: this.remove_check_digit_if_necessary(
component.identifier_code),
quantity: 1
};
}
}
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('components.load_failed'));
return [];
});
}));
}, (reason) => {
this.Toast.error(
this.gettextCatalog.getString('items.failed_load'));
return [];
} )
If you only want one item, find the first non-falsy element in the resulting array

Categories