Parse promises and loop of query - javascript

I cannot figure out how I ca apply Promise paradigm to my code.
My code doesn't work out how it should. The last 2 queries are not performed...
The first (mainQuery) is limited to <=5 items so it should be fast enough.
The pipeline should be:
query1.find()->for all elements found->if the element is of type 1 -> query2.count()->if count == 0-> save a new object
Could you please help me to fix it?
Thank you in advance,
Michele
mainQuery.find().then(
function(items){
for (var i = 0; i < items.length; i++) {
if(items[i].get("type") == 1){
var query = new Parse.Query("Invites");
//query.equalTo("userId","aaaaaaaa")
query.count().then(function(count){
console.log("why i don't see it in logs...??");
if (count == 0){
var invite = new Parse.Object("Invites");
//invite.set("userId", "aaaaaaa");
invite.save();
}
}, function(error){
response.error("Msgs lookup failed");
});
}
}
response.success(items);
},
function(error) {response.error("Msgs lookup failed");
});

I think the problem is that you're calling response.success before the other queries have been executed.
The code should look more like:
mainQuery.find().then(
function(items){
var promises = [];
for (var i = 0; i < items.length; i++) {
if(items[i].get("type") == 1){
var query = new Parse.Query("Invites");
//query.equalTo("userId","aaaaaaaa")
// first promise which needs to be resolved
var countPromise = query.count().then(function(count){
console.log("why i don't see it in logs...??");
if (count == 0){
var invite = new Parse.Object("Invites");
//invite.set("userId", "aaaaaaa");
// instead of an resolved object, we return another promise which will be resolved
// before entering the success part below
return invite.save();
} else {
// if there is nothing to do, we return an promise which will be resolved with a dummy value
return Promise.as('nothing to do');
}
});
promises.push(countPromise);
}
}
return Parse.Promise.when(promises).then(function (results) {
// we only get in here when all promises aboth has been resolved
response.success(items);
});
},
function(error) {
// if any of the requests fail, this method will be called
response.error("Msgs lookup failed");
});
You'll find some additional information on how to chain promises on the official documentation: https://parse.com/docs/js/guide#promises-promises-in-parallel

Related

Know when jqXHRs of an array are all completed

I'm trying to run some code once all the jqXHR elements of an array are completed (have either succeeded or failed).
You can see the full code here: http://jsfiddle.net/Lkjcrdtz/4/
Basically I'm expecting the always hook from here:
$.when
.apply(undefined, reqs)
.always(function(data) {
console.log('ALL ALWAYS', data);
});
to run when all the requests that were piled up there have either succeeded or failed. Currently, you can observe in the console that ALL ALWAYS is logged earlier.
A simple solution for modern browsers would be to use the newer fetch() API along with Promise.all()
var makeReq = function(url, pos) {
var finalUrl = url + pos;
// intentionally make this request a failed one
if (pos % 2 === 0) {
finalUrl = "https://jsonplaceholder.typicode.com/423423rfvzdsv";
}
return fetch(finalUrl).then(function(resp) {
console.log('Request for user #', pos);
// if successful request return data promise otherwise return something
// that can be used to filter out in final results
return resp.ok ? resp.json() : {error: true, status: resp.status, id: pos }
})
};
// mock API
var theUrl = "https://jsonplaceholder.typicode.com/users/";
var reqs = [];
for (var i = 1; i <= 5; i++) {
reqs.push(makeReq(theUrl, i));
}
Promise.all(reqs).then(function(results) {
console.log('---- ALL DONE ----')
// filter out good requests
results.forEach(function(o) {
if (o.error) {
console.log(`No results for user #${o.id}`);
} else {
console.log(`User #${o.id} name = ${o.name}`);
}
})
})

AngularJS - API Call in a Loop - getting the max value of an array and setting a response

This is my first time here, and I am really lost on what to do.
So I have a loop where I am making a post request to an API. I need to send some questions and get their rates match back. After that, I need to calculate the highest match and set an answer in some $scope variable to display on the screen.
The problem I have is that, since the call is asynchronous, sometimes it will display the wrong answer (because it's the last response returned).
I know it's asynchronous and I should have some callback, but I tried many ways and still can't figure out how to. All I want is to be able to "sort" the result so I can get the max number and display the associated answer AFTER all the calls are done. What I have so far is that:
for (var i = 0; i < $scope.possibleQuestions.length; i++) {
var compare = compareAPI($scope.results, $scope.possibleQuestions[i].question,
$scope.possibleQuestions[i].answer,
$scope.possibleQuestions[i].keywords,
$scope.possibleQuestions[i].similar,
function (score, question, answer, keywords, similar) {
$scope.compareAPI = score.average;
if ($scope.compareAPI >= 0.6) {
realAnswer = answer;
questionsAskedCount++;
$scope.answer = realAnswer;
} else {
var isMatch = matchKeywordAPI(question, keywords);
if (isMatch == 0) {
$scope.answer = "Please try asking another question!";
}
else {
//have to return a string just to know, bcause realAnswer is undefined in here, have to return callback function hahahahaha again, or set the answer in the match function
$scope.answer = realAnswer;
}
}
});
}
And the other function:
function compareAPI (possibleQuestion, possibleAnswer, fn) {
console.log("compare API");
var apiMatch = semanticService.getSemantic($scope.studentQuestion, possibleQuestion)
apiMatch.then(function (result) {
fn(result.data, possibleQuestion, possibleAnswer);
console.log(result.data);
}, function(error){
$scope.status = 'Unable to load question data: ' + error.message;
});
}
My biggest problem is that this part
if ($scope.compareAPI >= 0.6) {
realAnswer = answer;
questionsAskedCount++;
$scope.answer = realAnswer;
} else {
var isMatch = matchKeywordAPI(question, keywords);
if (isMatch == 0) {
$scope.answer = "Please try asking another question!";
}
else {
$scope.answer = realAnswer;
}
}
is random because of the async, so if the wrong answer is the last response, it will go to the 'else' and the answer gets wrong.
Any help will be very appreciated!!! Thanks!
Promises can done sequentially by chaining them:
var promise = $q.when(null);
for (let i=0; i<promiseArray.length; i++) {
promise = promise.then(function() {
//return promise to chain
return promiseArray[i];
});
};
promise.then(function() {
console.log("ALL Promises Done");
});
For more information, see AngularJS $q Service API Reference - Chaining Promises.

In calling URLs iteratively using http.get() and resolving using $q.all()

I am implementing this scenario where I have to fetch data from multiple URLs iteratively and process it with some business logic and display on screen. I am implementing this in the controller as it is a requirement. All is well until part-1 and I am getting the 6 promise objects in the promises array. But, I am not getting the data into metricData. I am seeing a null in the console while running in the browser. I am sure that the data is coming in the URL response. I feel I am doing something silly in the $q.all method. Is this correct?
var calculateMutationsInDepth = function(){
//Part-1
var promises=[];
var metricData=[];
for(var depth=0 ; depth<6 ; depth++){
var resourceUrl = urlService(depth);
promises.push($http.get(resourceUrl)
.then(function(response){
return response.data;
},function(status){
return status;
}));
}
//Part-2 Resolving the promise array below
$q.all(promises).then(function(data){
for(var eachResult=0; eachResult < data.length; eachResult++){
if(null != data[eachResult]){
var eachDataObject = data[eachResult];
//For debugging console.log(eachDataObject);
for(var objCount=0; objCount < eachDataObject.length; objCount++){
if(eachDataObject[objCount].scope === "PRJ" || eachDataObject[objCount].scope === "FIL")
metricData.push(eachDataObject[objCount]);
}
}
}
});
if(metricData != null){
analyzeMutationData(metricData); //Calling a function with the aggregated data array where business logic is present
}
};
calculateMutationsInDepth(); //Calling the above function
Yes, something silly.
As written, analyzeMutationData(metricData) is called synchronously whereas metricData is populated asynchronously inside the $q.all(promises).then() callback.
Also, as written the error handler function(status){ return status; } is inappropriate. Either :
omit the error handler entirely and allow any single $http error to prevent further processing in Part 2, or
return null, allowing processing in Part 2, and the if(dataObject != null) test in part 2 to filter out any such error.
Here's the revised code with a few other tidies and a demonstration of what can be done if calculateMutationsInDepth() returns a promise.
var calculateMutationsInDepth = function() {
//Part-1
var depth, promises = [];
for(depth=0; depth<6; depth++) {
promises.push($http.get(urlService(depth))
.then(function(response) {
return response.data;
}, function(error) {
return null; // error recovery - `dataObject` below will be null
}));
}
//Part-2 Aggregate the promises, extract metric data and apply business logic
return $q.all(promises).then(function(data) { // note `return` here
var dataObject, i, j, metricData = [];
for(i=0; i<data.length; i++) {
dataObject = data[i];
if(dataObject != null) {
for(j=0; j<dataObject.length; j++) {
if(dataObject[j].scope === "PRJ" || dataObject[j].scope === "FIL") {
metricData.push(dataObject[j]);
}
}
}
}
// Analyse here, inside the .then()
if(metricData.length > 0) { // metricData is an array and will never be null, therefore test metricData.length.
return analyzeMutationData(metricData);
}
return null;
});
};
calculateMutationsInDepth().then(function(analysis) {
// all complete
// `analysis` is either null or whatever `analyzeMutationData(metricData)` returned.
}).catch(function(error) {
console.log(error);
});
Hope this helps you out! Let me know if it doesn't.

Check if a promise finished in Javascript?

I am using PDF.js to extract text content from a PDF which I will use next for some more processing, For this,
var complete=0;
var full_text="";
var PDF_render = PDFJS.getDocument("x.pdf").then(function(pdf) {
var page_text = {};
for (i = 1; i <= pdf.numPages; i++){
pdf.getPage(i).then( function(page){
var n = page.pageNumber;
page.getTextContent().then( function(textContent){
var page_text_part = "";
textContent.items.forEach(function (textItem){
page_text_part += textItem.str;
page_text_part += " ";
});
page_text[n] = page_text_part + "\n\n";
++complete;
if (complete == pdf.numPages){
for( var j = 1; j <= pdf.numPages; j++)
full_text += page_text[j];
}
});
});
}
});
The issue is that PDF.js returns promises and they are executed asynchronously, however I need to perform some post processing on the returned text. For this I need to wait for the promises to fully execute and only then move on. How does one achieve this? Please help.
Unfortunately this question asks one thing in title (how to check if Promise finished), and another in the body (how to continue once Promise is finished). Google points to this when searching for how to check if Promise is finished, so I'm answering the question in the title.
To check if a promise if is finished, you can use Promise.race - it will not wait for pending promise if given non-promise value, e.g.
const statusOrValue = await Promise.race([myPromise, 'pending']);
if (statusOrValue === 'pending') {
// Not yet finished
}
Note that this method gives the result asynchronously - await is necessary to check if promise is finished. I don't know if there is a reliable way to check whether promise is completed synchronously.
We can use a much more direct approach, no need for counters etc.
Two things - promises chain, and waiting for multiple promises is done with Promise.all:
var pdf = PDFJS.getDocument("x.pdf");
pdf.then(function(pdf) { // wait for the PDF to load
var pages = [];
for (i = 1; i <= pdf.numPages; i++){ // for each pages
pages.push(pdf.getPage(i).then(function(page){ // push the promise
return page.getTextContent();
}).then(function(textContent){
var page_text_part = "";
textContent.items.forEach(function (textItem){
page_text_part += textItem.str + " ";
});
return page_text_part + "\n\n"; // you can use return values
}));
}
return Promise.all(pages); // wait for all of them to be done
}).then(function(results){
// you can access all the results here
// containing all pages
});
You can use Promise.all to wait for multiple promises to have resolved.
In your case, the code should look like
PDFJS.getDocument("x.pdf").then(function(pdf) {
var pages = [];
for (var i = 1; i <= pdf.numPages; i++) {
pages.push(pdf.getPage(i).then(function(page) {
return page.getTextContent();
}).then(function(textContent) {
return textContent.items.map(function(textItem){
return textItem.str;
}).join(" ") + " \n\n";
});
return Promise.all(promises);
}).then(function(page_texts) {
var full_text = page_texts.join("");
// now do something with the result
});

How to reorder execution of asynchronous functions?

if (option == 'Follow All') {
for (var i = 0; i < userArray.length; i++) {
followUser(params..);
}
// How to get this part to execute after followUser is done? (Basically when the for loop finishes)
alert("There was a problem processing your request on Twitter to follow the following users: " + $('#errored-users').val());
$('#errored-users').val('');
}
How can I call this first multiple times and wait for it to finish?
var followUser = function(params..) {
$.post('/api/1.0/followUser.php', {
'user_to_follow': username,
'username': user
}).done(function(data) { {
if (!is_batch) {
alert("There was a problem processing your request on Twitter to follow #" + username);
} else {
//This currently gets executed last?
var names = $('#errored-users').val();
if (names == "") {
names = '#' + username + ', ';
} else {
names += '#' + username + ', ';
}
$('#errored-users').val(names);
}
};
Since you are already using jQuery, you can easily use the AJAX requests/promises and wait for all of them to complete. $.when can help you a lot with this:
var followUser = function(params..) {
// return the promise!
return $.post('/api/1.0/followUser.php', { ... });
};
if (option == 'Follow All') {
var promises = [];
for (var i = 0; i < userArray.length; i++) {
promises.push(followUser(...));
}
$.when.apply(null, promises)
.done(function() {
// all users were successfully followed
})
.fail(function() {
// at least one follow failed; no information about the others
alert("There was a problem processing your request...");
$('#errored-users').val('');
});
}
This will call the .done handler when all requests have completed, but it will call the .fail handler as soon as just one has failed.
If instead you want some handler to run after all requests have completed (either success or failure) you 'd need to do it more manually, for example:
var followUser = function(params..) {
// return the promise!
return $.post('/api/1.0/followUser.php', { ... });
};
if (option == 'Follow All') {
var outcomes = { done: [], failed: [] };
var total = userArray.length;
function allFinished() {
return outcomes.done.length + outcomes.failed.length == total;
}
for (var i = 0; i < total; i++) {
followUser(...)
.done(function() {
outcomes.done.push(username);
})
.fail(function() {
outcomes.failed.push(username);
})
// this must come last
.always(function() {
if (allFinished) {
// outcomes contains the results
}
})
}
}
This will still use jQuery's notion of a request having succeeded or failed, which is based on Twitter's HTTP response codes. If you want to customize this behavior you can amend followUser as such:
var followUser = function(params..) {
return $.post('/api/1.0/followUser.php', { ... })
.then(
// first argument handles HTTP response successes, but you can
// convert them to failures here:
function(data) {
if (convertSuccessToFailure) {
return $.Deferred.reject(data);
}
});
};
As of jQuery 1.5 any of the $.ajax family of functions return a promise - and you can combine multiple promises into a new promise that will be resolved when all the child promises are resolved using $.when:
function followUser(/* params */) {
return $.post('/api/1.0/followUser.php', {
user_to_follow: username,
username: user
});
}
var userRequests = [];
for (var i = 0, l = userArray.length; i < l; i++) {
userRequests.push(followUser(/* params */));
}
$.when.apply($, userRequests).then(function(data) { /* etc. */ });
You could define a global variable which holds the number of calls to followUser:
if (option == 'Follow All') {
var countUsers = userArray.length;
for (var i = 0; i < countUsers; i++) {
followUser(params..);
}
}
Then you change the anonymous function to count backwards and execute your last statement if all users are done:
function(data) {
if (!is_batch) {
alert("There was a problem processing your request on Twitter to follow #" + username);
} else {
(...)
}
countUsers--;
if(countUsers == 0){
alert("There was a problem processing your request on Twitter to follow the following users: " + $('#errored-users').val());
$('#errored-users').val('');
}
};
A potential solution for this is to use Promises (see here for an in-depth explanation). It provides a new style of coding in Javascript that effectively enables you to make asynchronous code synchronous. (This is a big simplification of Promises - see here for an article explaining it a little bit more).
There are various implementations which you could use. The one I most use is found here: https://github.com/cujojs/when. The examples provided within it's wiki demonstrates the power of promises (see here).
The basic outline for your code using when.js would look and read something like this:
if (option == 'Follow All') {
var deferreds = [];
for (var i = 0; i < userArray.length; i++) {
deferreds.push(followUser(params..));
}
when.all(deferreds).then(function everythingWasFine(suceededUsernames) {
//do something with the responses e.g.
alert(succeededUsernames.length + ' users were followed');
},
function somethingWentWrong(failedUsernames) {
alert("There was a problem processing your request on Twitter to follow the following users: " + failedUsernames.join(','));
});
}
var followUser = function(params..) {
var defer = when.defer();
$.post('/api/1.0/followUser.php', {
'user_to_follow': username,
'username': user
}).done(function(data) {
if (failure) {
defer.reject(username);
} else {
defer.resolve(username);
}
});
return when.promise;
};

Categories

Resources