if I have a code like this
if (request.params.friends != null)
{
_.each(request.params.friends, function(friend) {
// create news
var News = Parse.Object.extend("News");
var news = new News();
news.set("type", "ask");
news.save();
});
response.success();
}
and the length of request.params.friends is 2, does the second news get saved for certain? If not, how to make sure it gets saved? I looked at Parse.Promise documentation and in all the examples, the loop is inside a query or a save. Do I need to save the first news first and then create the Promise? I still don't get how "asynchronous" works.. Does the response.success() work like a return or break?
The loop does get executed twice.
response.success() acts like a return.
The asynchronous magic is in the "save" method. When "save" is called, the Parse.com says, "ok, you want me to save it. I'll save it, but not now. For now, here is a promise that I'll save it later." The save method returns an Promise object and the promise will be fulfilled when the object is actually saved.
So what happens is a little like
First time through the loop: create friend #1.
Ask Parse to save friend #1.
Second time through the loop: create friend #2.
Ask Parse to save friend #2.
Return successful response.
Parse actually saves friend #1 and friend #2
It's been a while since I've used Parse, but I'm not sure usually both the friend objects would actually get saves. Calling response.success() could kill work-in-progress. Here is an alternative implementation:
var objectsToSave = _.collect(request.params.friends, function(friend) {
var news = new News();
news.set({type : "ask"});
return news;
});
Parse.Object.saveAll(objectsToSave, {
success: function(list) {
// All the objects were saved.
response.success();
},
error: function(error) {
// An error occurred while saving one of the objects.
},
});
The saveAll function saves all the objects at once. It's usually faster than saving objects one-at-a-time. In addition to providing saveAll with the objects to save, we provide it an object with a success function and an error function. Parse.com promises only to call the functions AFTER the save is complete (or it experienced an error).
There are a few other things going on. The Parse.Object.extend statement belongs in a different place in your code. Also, the set function doesn't take a list of strings. It takes a JavaScript object.
Related
I'm creating a web app with Vue.js (this is the first time I use it). The app is basically a multi user real time quiz, in which every user have to choose a role and to answer questions related with his role. I use a collection in cloud firestore database to store questions associated to each role and answers associated to each question. Moreover each answer is characterized by a field "nextQuestion" that contains the id of the next question to visualize, a field "nextUser" that contains the id of the next user at which this new question is related (these fields are used in queries to select the next question and its possible answers) and a boolean field "default" that indicates, if true, the answer that is chosen in the case user don't answer the question within the set time (others to a field indicating the text of the answer). I get questions and answers with a query to visualize them on the webapp.
My problem is related to the situation in which the user doesn't answer a question within the set time (meanwhile if a user selects an answer within the set time, I haven't problems). When the time for an answer expires, I call this function:
CountTerminated: function () {
if(this.optionSelected == false){ //optionSelected is a component variable that informs if a user has selected or not an answer
this.onNotSelectedAnswer(this.getDefaultAnswer())
}
else{
this.onClickButtonConfirm() //function called if the user selects an answer within the set time
}
}
The function getDefaultAnswer() gets the fields (among which "nextUser" and "nextQuestion") of the default answer associated with the current question (through a query) and return them through a variable:
getDefaultAnswer(){
var data
db.collection("Utenti").doc(this.userId).collection("Domande").doc(this.questionId).collection("Risposte").where("default","==",true).get().then(querySnapshot =>{
querySnapshot.forEach(doc=>{
data = doc.data()
})
})
return data
},
the function onNotSelectedAnswer(data) mainly takes in input the value returned by getDefaultAnswer(), it assigns "data" to the component variable answerId and it updates the value of the component variable "userId" (that informs about the role of the user who have to answer the current question),the value of the component variable "questionId" (that contains the id of the current question) and the value of questionValue(that contains the text of the current question) using functions setUserId(), setQuestionId(), setQuestionValue()
onNotSelectedAnswer: function(data){
if(this.userChoice == this.userId){
this.answerId = data
this.setUserId(this.answerId.nextUser)
this.setQuestionId(this.answerId.nextQuestion)
this.setQuestionValue()
this.optionSelected = false
this.answers=[]
this.isActive = ""
this.getAnswers() //function used to query (using updated values of userId and questionId variable) the DB to obtain a question and its related possible answers
var a = this.channel.trigger('client-confirmEvent',{ user: this.userId, question : this.questionId, questionval: this.questionValue})
console.log(a)
}
}
The problem is related to the fact that in onNotSelectedAnswer() function, answerId is "undefined" instead of containing the result of the query and therefore the data that I will use to upload the new question. I don't understand which is the error, I hope that you can help me.
The problem is that the Firestore query is asynchronous but you aren't waiting for the response before continuing. Effectively what you have is this:
getDefaultAnswer () {
var data
// This next bit is asynchronous
db.doLotsOfStuff().then(querySnapshot => {
// This callback won't have been called by the time the outer function returns
querySnapshot.forEach(doc => {
data = doc.data()
})
})
return data
},
The asynchronous call to Firestore will proceed in the background. Meanwhile the rest of the code in getDefaultAnswer will continue to run synchronously.
So at the point the code reaches return data none of the code inside the then callback will have run. You can confirm that by putting in some console logging so you can see what order the code runs in.
The use of then to work with asynchronous code is a feature of Promises. If you aren't already familiar with Promises then you should study them in detail before going any further. Here is one of the many guides available:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Using_promises
The bottom line is that you cannot force the getDefaultAnswer method to wait for the asynchronous action to complete. What you can do instead is to return a suitable Promise and then wait for that Promise to resolve before you call onNotSelectedAnswer. It might look something like this:
getDefaultAnswer () {
// We return the Promise chain from getDefaultAnswer
return db.doLotsOfStuff().then(querySnapshot => {
var data = null
// I have assumed that forEach is synchronous
querySnapshot.forEach(doc => {
data = doc.data()
})
// This resolves the Promise to the value of data
return data
})
},
It is important to appreciate that the method getDefaultAnswer is not attempting to return the value of the data. It is instead returning a Promise that will resolve to the value of the data.
Within CountTerminated you would then use it like this:
this.getDefaultAnswer().then(defaultAnswer => {
this.onNotSelectedAnswer(defaultAnswer)
})
or if you prefer:
this.getDefaultAnswer().then(this.onNotSelectedAnswer)
The latter is more concise but not necessarily clearer.
You could also write it using async/await but I wouldn't advise trying to use async/await until you have a solid grasp of how Promises work. While async/await can be very useful for tidying up code it is just a thin wrapper around Promises and you need to understand the Promises to debug any problems.
The code I've suggested above should work but there is a delay while it waits for the asynchronous request to complete. In that delay things can happen, such as the user may click on a button. That could get you into further problems.
An alternative would be to load the default answer much sooner. Don't wait until you actually need it. Perhaps load it as soon as the question is shown instead. Save the result somewhere accessible, maybe in a suitable data property, so that it is available as soon as you need it.
Disclaimer: I'm new to ES6 and promises in general so its possible my approach is fundamentally wrong.
The Problem
I have an api that returns paged data. That is it returns a certain number of objects for a collection and if there's more data it returns a Next property in the response body with the url to get the next set of data. The data will eventually feed the autocomplete on a text input.
To be specific, if my collection endpoint is called /Tickets the response would look like:
{
Next: "/Tickets?skip=100&take=100"
Items: [ ... an array of the first 100 items ... ]
}
My current solution to get all the ticket data would be to
Make a new promise for the returning the whole combined set of data
"Loop" the ajax calls by chaining dones until there is no more Next value
Resolve the promise
getTickets(filterValue) {
let fullSetPromise = new Promise();
let fullSet = [];
// This gets called recursively
let append = function(previousResult) {
fullSet.concat(previousResult.Items);
// Loop!
if(previousResult.Next) {
$.ajax({
url: previousResult.Next
})
.done(append)
.catch(reason => fullSetPromise.reject(reason));
}
else {
fullSetPromise.resolve(fullSet);
}
}
// We set things off by making the request for the first 100
$.ajax({
url: `/Tickets?skip=0&take=100&filter=${filterValue}`
})
.done(append)
.catch(reason => fullSetPromise.reject(reason));
return fullSetPromise;
}
Eventually the promise is used by the frontend for autocomplete on a text input. Ideally I'd like to be able to abort the previous call when new input comes in.
inputChanged(e) {
this.oldTicketPromise.abort();
this.oldTicketPromise =
Api.GetTickets(e.target.value).then(updateListWithResults);
}
I am aware of debouncing. But that just means the problem happens every n seconds instead of on every key press.
I know the jqxhr object has an abort() property on it and I'd like that to be available to the caller somehow. But because there are multiple jqXHR objects used in GetTickets I'm not sure how to handle this.
So my main 2 questions are:
What is the appropriate way to consume paged data from an api while returning a promise.
How can the returned promise be made abortable?
Side question:
I feel like if I don't catch the errors then my "wrapper" promise will swallow any thrown errors. Is that a correct assumption?
Note the javascript code might have errors. It's mostly demonstrative for the logic.
Edit: Solved
I have solved this by combining this answer https://stackoverflow.com/a/30235261/730326 with an array of xhrs as suggested in the comments. I will add a proper answer with code when I find the time.
This is not going to be the best parse.com question out there, but it's the first time I write something in javascript and I can't figure out what's wrong.
In my db I have ParsePosts, ParseUsers and ParseComments. When I delete a post, I want the following to be done:
Task 1: find the user who wrote the post, and decrement his "posts" counter;
Task 2: delete all ParseComments that were related to the post to-be-deleted;
Task 3: find user who "liked" the post, and decrement their "likes" counter.
I'm trying as follows (renaming some variables so there might be typos):
Parse.Cloud.beforeDelete("ParsePost", function(request,response) {
var post = request.object;
var user = post.get("author");
user.increment("posts",-1);
user.save().then(function(success) {
var query = new Parse.Query("ParseComment");
query.equalTo("post",post);
return query.find();
}).then(function(comments) {
for (var i = 0; i < comments.length; i++) {
var comment = comments[i];
comment.destroy();
}
var query2 = new Parse.Query("ParseUser");
query2.equalTo("postsLiked",post); //"postsLiked" is a relation field
return query2.find();
}).then(function(users) {
for (var i = 0; i < users.length; i++) {
var u = users[i];
u.increment("postsLikedCount",-1);
u.save();
}
response.success();
});
});
However, deleting fails with the message: Result: success/error was not called. I have no idea why.
The way I dealt with promises, as far as I understood (not much), should call the next then() even if there's an error in one of the steps before. In other words, the three tasks are not really consequential - I'd like the function to:
try to accomplish each task;
call success() no matter the output of the tasks: I want the post to be deleted even if, say, comments.length == 0.
How should I accomplish that, and why my function is not reaching response.success()?
As a side note, I haven't even found a way to debug - I use parse deploy and then look at the logs section in parse.com, but I can't manage to write anything into it. Namely, console.log() gives no output.
One of your promises is throwing an error, and you do not have any .then()'s that catch that error. .then() either takes 1 or 2 arguments, both of which are functions. If there is only 1, it takes the resolved value of a promise. If there are 2, the second one takes the rejected value of a promise. One of your promises is being rejected, but the promise is never being caught. Add this to the end of your chain:
.then( function(){},
function(error)
{
response.error("There was an error: " + error.message);
}
);
Rejected promises skip any link in the promise chain that does not contain an error handler, so having this one link at the end will catch any error from earlier links. Even if you call response.success() and don't return a promise, the success function of the next chain will be called. You just won't be passing anything in to the variable, so it'll be null/undefined
Edit - Looking at your code, I bet it's going to be an issue pretty early on. You never fetched the user, so it's just an empty object with nothing but an object id right now.
Edit2 - Also, query.find() only returns the first 1000 results. If you want to do something to every single object, you should be using query.each().
Is there a way to wait on a promise so that you can get the actual result from it and return that instead of returning the promise itself? I'm thinking of something similar to how the C# await keyword works with Tasks.
Here is an example of why I'd like to have a method like canAccess() that returns true or false instead of a promise so that it can be used in an if statement. The method canAccess() would make an AJAX call using $http or $resource and then somehow wait for the promise to get resolved.
The would look something like this:
$scope.canAccess = function(page) {
var resource = $resource('/api/access/:page');
var result = resource.get({page: page});
// how to await this and not return the promise but the real value
return result.canAccess;
}
Is there anyway to do this?
In general that's a bad idea. Let me tell you why. JavaScript in a browser is basically a single threaded beast. Come to think of it, it's single threaded in Node.js too. So anything you do to not "return" at the point you start waiting for the remote request to succeed or fail will likely involve some sort of looping to delay execution of the code after the request. Something like this:
var semaphore = false;
var superImportantInfo = null;
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
superImportantInfo = results;
semaphore = true;
});
while (!semaphore) {
// We're just waiting.
}
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + superImportantInfo);
But if you try that in a browser and the call takes a long time, the browser will think your JavaScript code is stuck in a loop and pop up a message in the user's face giving the user the chance to stop your code. JavaScript therefore structures it like so:
// Make a remote request.
$http.get('some wonderful URL for a service').then(function (results) {
// Code we're trying to avoid running until we know the results of the URL call.
console.log('The thing I want for lunch is... " + results);
});
// Continue on with other code which does not need the super important info or
// simply end our JavaScript altogether. The code inside the callback will be
// executed later.
The idea being that the code in the callback will be triggered by an event whenever the service call returns. Because event driven is how JavaScript likes it. Timers in JavaScript are events, user actions are events, HTTP/HTTPS calls to send and receive data generate events too. And you're expected to structure your code to respond to those events when they come.
Can you not structure your code such that it thinks canAccess is false until such time as the remote service call returns and it maybe finds out that it really is true after all? I do that all the time in AngularJS code where I don't know what the ultimate set of permissions I should show to the user is because I haven't received them yet or I haven't received all of the data to display in the page at first. I have defaults which show until the real data comes back and then the page adjusts to its new form based on the new data. The two way binding of AngularJS makes that really quite easy.
Use a .get() callback function to ensure you get a resolved resource.
Helpful links:
Official docs
How to add call back for $resource methods in AngularJS
You can't - there aren't any features in angular, Q (promises) or javascript (at this point in time) that let do that.
You will when ES7 happens (with await).
You can if you use another framework or a transpiler (as suggested in the article linked - Traceur transpiler or Spawn).
You can if you roll your own implementation!
My approach was create a function with OLD javascript objects as follows:
var globalRequestSync = function (pUrl, pVerbo, pCallBack) {
httpRequest = new XMLHttpRequest();
httpRequest.onreadystatechange = function () {
if (httpRequest.readyState == 4 && httpRequest.status == 200) {
pCallBack(httpRequest.responseText);
}
}
httpRequest.open(pVerbo, pUrl, false);
httpRequest.send(null);
};
I recently had this problem and made a utility called 'syncPromises'. This basically works by sending what I called an "instruction list", which would be array of functions to be called in order. You'll need to call the first then() to kick things of, dynamically attach a new .then() when the response comes back with the next item in the instruction list so you'll need to keep track of the index.
// instructionList is array.
function syncPromises (instructionList) {
var i = 0,
defer = $q.defer();
function next(i) {
// Each function in the instructionList needs to return a promise
instructionList[i].then(function () {
var test = instructionList[i++];
if(test) {
next(i);
}
});
}
next(i);
return defer.promise;
}
This I found gave us the most flexibility.
You can automatically push operations etc to build an instruction list and you're also able to append as many .then() responses handlers in the callee function. You can also chain multiple syncPromises functions that will all happen in order.
I am using PhoneGap and jQuery Mobile. I am trying to get some JSON data from a remote location and then populate a local WebSQL database with it. Here is my JS function:
function getLocations() {
var tx = window.openDatabase('csdistroloc', '1.0', 'Distro DB', 1000000);
tx.transaction(function(tx) {
tx.executeSql('DROP TABLE IF EXISTS locations'); //this line works!
tx.executeSql('CREATE TABLE IF NOT EXISTS locations (id, name, address, postalcode, phone, category)'); //this line works!
$.ajax({
url: "http://mydomain.com/api.php",
dataType: 'json',
data: { action: "getlocations" },
success: function(data) {
tx.executeSql("INSERT INTO locations (id, name, address, postalcode, phone, category) VALUES (2,'cheese','232','seven',5,6)"); //this line produces an error
}});
}, dberror, dbsuccess);
}
Running the above function gives me an error "INVALID_STATE_ERR: DOM Exception 11" on the line noted above. It does the same thing when I am actually trying to use the returned JSON data to insert data. I have also tried the $.getJSON technique with the exact same result.
Any advice would be appreciated!
Although the accepted answer is correct, I would like to expand upon it because I encountered the same problem and that answer doesn't say why it doesn't work as the OP had it.
When you create a transaction in Web SQL, the transaction processing remains alive only so long as there are any statements queued up in the transaction. As soon as the pipeline of statements in the transaction dries up, the engine closes (commits) the transaction. The idea is that when the function(tx) { ... } callback runs,
It executes all of the statements it need to. executeSql is asynchronous, so it returns immediately even though the statement has not yet been executed.
It returns control back to the Web SQL engine.
At this point the engine notices that there are statements queued up and runs them to completion before closing the transaction. In your case, what happens is this:
You call executeSql twice to queue up two statements.
You request something through ajax.
You return
The engine runs the two statements that it has queued up. The ajax request is also running asynchronously but it must access the network which is slow so it likely has not completed yet. At this point, the statement queue is empty and the Web SQL engine decides that it's time to commit and close the transaction! It has no way of knowing that there is going to be another statement coming later! By the time the ajax call returns and it attempts to execute the INSERT INTO locations, it's too late, the transaction is already closed.
The solution suggested by the accepted answer works: don't use the same transaction inside the ajax callback but create a new one. Unfortunately, it has the pitfall you would expect from using 2 transactions instead of 1: the operation is no longer atomic. That may or may not be important for your application.
If atomicity of the transaction is important for you, your only 2 recourses are:
Do everything (all 3 statements) in one transaction inside the ajax callback.
This is what I recommend. I think it's very likely that waiting until after the ajax request completes before creating the table is compatible with your application requirements.
Perform the ajax request synchronously as explained here.
I don't recommend that. Asynchronous programming in JavaScript is a good thing.
By the way, I encountered the problem in the context of Promises, in code that looked something like this:
// XXX don't do this, it doesn't work!
db.transaction(function(tx) {
new Promise(function(resolve, reject) {
tx.executeSql(
"SELECT some stuff FROM table ....", [],
function(tx, result) {
// extract the data that are needed for
// the next step
var answer = result.rows.item( .... ).some_column;
resolve(answer);
}
)
}).then(function(answer) {
tx.executeSql(
"UPDATE something else",
// The answer from the previous query is a parameter to this one
[ ... , answer, .... ]
)
});
});
The problem is that, with promises, the chained .then() clause is not run immediately upon resolution of the original promise. It is only queued for later execution, much like the ajax request. The only difference is that, unlike the slow ajax request, the .then() clause runs almost immediately. But "almost" is not good enough: it may or may not run soon enough to slip in the next SQL statement into the queue before the transaction gets closed; accordingly, the code may or may not produce the invalid state error depending on timing and/or browser implementation.
Too bad: Promise would have been useful to use inside SQL transactions. The above pseudo-example can easily be rewritten without promises, but some use cases can greatly take advantage of chains of many .then()s as well as things like Promise.all that can make sure that an entire collection of SQL statements run in any order but all complete prior to some other statement.
I would first suggest not naming your database 'tx' but rather db or database. This could be a variable naming problem since both the function parameter and your database variables are called "tx"
EDIT: I had this same problem and solved it by making the query within the callback it's own transaction. Like so:
success: function(data) {
tx.transaction(function(transaction){
transaction.executeSql("INSERT INTO locations (id, name, address, postalcode, phone, category)
VALUES (2,'cheese','232','seven',5,6)"); //now more DOM exception!
}
}}
I think the problem is by the time the callback is fired the outer transaction has completed because webSQL's transactions are not synchronous.
We do have a way to lock the transaction, while you do any AJAX or other async operation. Basically before calling AJAX you need to start a dummy db operation and on success of that operation check if AJAX is done or not, and again call the same dummy operation till your AJAX is done. When AJAX is done you can now reuse the transaction object do next set of executeSQLs. This approach is thoroughly explained in this article here. (I hope someone will not delete this answer too, as someone did earlier on a similar question)
Try this approach