Backbone collection.create and promises - javascript

Im currently implementing a Purchase Order type View. Where I have a PurchaseOrder table and a PurchaseOrderLine table for the items. The first thing I do when the use presses the save button I first save the Purchase Order and then I retrieve the PurchaseOrderID and save to each individual PurchaseOrder item. The problems is the following:
Promise.resolve( app.PurchaseOrder.create(formData) ).then(function(response){
purchaseOrderID = response.collection.models[0].attributes.id;
for(var key in formLineData){
if(formLineData.hasOwnProperty(key)){
formLineData[key]['requestID'] = purchaseOrderID;
app.PurchaseOrderLines.create(formLineData[key]);
}
}
}).catch(function(error){
console.log(error);
})
formData is the PurchaseOrder data, formLineData is the PurchaseOrderLine Data(I do the for loop to insert requestIDs to all items).
I am using a Promise because collection.fetch does not return a promise on Backbone(I think my implementation isn't quite correct because Promise.resolve() is use to make thenables a promise and in this case it isn't). The problem is that when the save button is clicked the then part passes even PurchaseOrder hasn't been created. So when it gets to the PurchaseOrderLine.create, all the items are saved without a PurchaseOrderID. I have two options:
Add a server validation for this. The problem with this is that everytime is going to return an error and this can be bothersome to the user.
Add a setTimeout to at least wait a couple seconds for the write to over on the server.
Could please shed a light on this topic.

you can try something like this
app.PurchaseOrder.create(formData).then(function (response) {
var purchaseOrderID = response.collection.models[0].attributes.id;
return new Promise(async function (resolve) {
for (var key in formLineData) {
if (formLineData.hasOwnProperty(key)) {
formLineData[key]["requestID"] = purchaseOrderID;
await app.PurchaseOrderLines.create(formLineData[key]);
}
}
resolve()
});
});
or maybe doing something like this using Promise.all
Promise.all(formLineData.map(()=>app.PurchaseOrderLines.create(formLineData[key])))

Related

WebSocket response as Promise with multiple messages problem

I am using angularjs for a web site where I have to download data every seccond. To do that I am using a WebSocket, the problem is that I am sending a lot of different requests of different types. To elaborate the request I use Promises but some times the answers get to the wrong function. Let me make an example:
So I have a service that is making the request and gets the result as Promise.
socketService.$inject = [];
function socketService(){
let service = this;
let server = new WebSocket('ws://localhost:8090');
let isOpen = false;
server.onopen = function(){
isOpen = true;
};
server.onerror = function(){
//TODO handle error
};
service.sendRequest = function(action, data){
return new Promise(function (resolve, reject) {
if (isOpen) {
server.send(encodeRequest(action, data));
server.onmessage = function (message) {
resolve(JSON.parse(message.data));
}
}
server.onopen = function () {
server.send(encodeRequest(action, data));
server.onmessage = function(message) {
resolve(JSON.parse(message.data));
isOpen = true;
}
};
server.onerror = function (error) {
reject(error);
}
})
};
}
And I use it like this:
socketService.sendRequest('get_events', {})
.then(function (response) {
//DO SOMETHING WITH THE RESPONSE
return socketService.sendRequest('get_history', {
fromDate: value,
toDate : value,
tag : value,
event : value
})
})
.then(function (response) {
//DO SOMETHING WITH THE RESPONSE
});
The problem is that if I make the requests like this (when one promise is resolved I call the other it works perfectly) but if instead I do for example some requests every second and then in the same time I make a new request (socketService.sendRequest(...)) the results get messed up.
I figured out that the problem is that if I make a request and before I get the result of the first request I make another one, the response of the first request goes to the second one.
I don't understand why this happens because I return every time a new Promise, so it should resolve the response of that Promise instead of resolving the response of the next Promise.
I read that once a promise is resolved it cannot be used again (it returns the last result over and over) but I create a new one every time.
Can someone explain to me what happens and how I could fix this?
You'll need to put a unique ID into the messages so that you can ensure that each response gets correlated to the correct request. But before diving into coding this, you may want to look at json rpc 2.0. This is a standard, with a couple of different javascript implementations, that will handle most of this for you. I have had good luck with https://github.com/jershell/simple-jsonrpc-js
You also asked why your Promise code is not working the way you expect it to. The answer is... well, Promise doesn't work the way you expect it to. :-) OK, let me try to be more helpful. Your Promise code is altering the server's callback methods (onOpen, onMessage, etc.) so that they will call the promise's "resolve" function. The problem is that, even though you have multiple promises, you only have one server. So each Promise you create just points the server callback methods to a new resolve function.
But... skip all that. Someone else has already figured this out for you. Use JSON RPC 2.0.
Duncan
I was also facing similar issues, as WebSocket message work as events. I have written one library where we can write Promise based send and receive the message.
https://github.com/pathikdevani/linka

React-Redux Javascript app - trying to get lots of API calls to fire in order

I'm having an issue with successive API calls (using JQuery's AJAX) to two different APIs in order to build objects with certain attributes. Here's the summary of my app and what I'm trying to do:
The user enters in the name of an actor or director, and the app is meant to return a total of five movies, each of which has certain attributes like title, overview, year, budget, revenue, and a link to a YouTube preview. I'm using The Movie Database API, plus the YouTube API for the YouTube link.
Here's the order of how things currently to work, with all of this happening in the action creator of the Redux app:
Actor name gets sent to the TMDB API -- returns ActorID number
ActorID number gets sent to the TMDB API -- returns 20 movies with: title, overview, year, poster link, and MovieID number
For EACH movie in that list, the MovieID number gets sent to the API -- returns more attributes: budget, revenue, and IMDB-ID (to use in a link later)
Also for EACH movie in step 2, the title gets sent to the YouTube API -- returns a link to the preview.
Once all of this information is assembled for each movie, I want to return the first five movies and dispatch them as the action payload to the Redux store.
I'm using some promises, and I've tried everything I could think of in terms of rearranging the flow of functions, but I can't get all the information I need with one click of the submit button. The funny thing is, it works with TWO clicks of the submit button, I think because by then all the async AJAX calls are finally done. But after the first click, I have an empty array where the movie objects should be.
Here's some code that should summarize what things look like:
var personId
var movies = []
function actorByRating(UserInput) {
Step 1: get actor ID number:
function searchActors() {
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/search/person?query=${UserInput}&api_key=<key>`
}).done(function(response){
personId = response.results[0].id
})
}
Step 2: Use Actor ID to get list of movies, start assigning them attributes:
function getMovies() {
$.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/discover/movie?with_cast=${personId}&vote_count.gte=20&sort_by=vote_average.asc&budget.desc&api_key=<key>&include_image_language=en`
}).done(function(response) {
response.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
getMovieInfo(movie) //step 3
getYouTube(movie) //step 4
saveMovie(movie)
})
})
}
function saveMovie(movie){
movies.push(movie)
}
Step 3 function, takes in a movie object as an argument:
function getMovieInfo(m){
return $.ajax({
method: "GET",
url: `https://api.themoviedb.org/3/movie/${m.movieId}?&api_key=<key>&append_to_results=imdb_id`
}).done(function(response) {
m.revenue = response.revenue
m.budget = response.budget
m.imdbId = response.imdb_id
})
}
Step 4 function, to get Youtube link. Also takes a movie object:
function getYouTube(movie){
$.ajax({
method: "GET",
url: `https://www.googleapis.com/youtube/v3/search?part=snippet&q=${movie.title.split(" ").join("+")}+trailer&key=<key>`
}).done(function(yt){
movie.youtubeLink = `http://www.youtube.com/embed/${yt.items[0].id.videoId}`
})
}
After this, the filtering functions work fine, when they have an array of movies to work with. The problem is, I think, all these successive API calls keep firing before the previous ones are done, and the latter ones need info from the earlier ones to search with. Thus, when I click submit the first time, the final movies array is empty, so the dispatched payload is an empty array. THEN the movie objects get filled in, so when you click submit again, the movies are already there to work with, and the rest of the app works fine.
I've tried everything I can think of to slow the process down, chain promises together (which doesn't work because Step 2 has to run for several movies, i.e. the return values of each function keep changing, so I can't ".then" them), reorganizing the information that comes in...but I can't get it to give me movie objects with all the attributes I need by the time the filtering functions actually run to create the proper payload.
Any help or suggestions would be greatly appreciated!
(Note: the "key" stuff above is just placeholder text)
UPDATE:
I changed the code to basically the following:
searchActors()
.then(function(response){
const actorId = response.data.results[0].id
return actorId
})
.then((personID) => {
return getMoviesFromPersonID(personID)
})
.then(function(response) {
const movieList = []
response.data.results.forEach((m) => {
var movie = {}
movie.title = m.title
movie.year = m.release_date.split("-")[0]
movie.movieId = m.id
movie.overview = m.overview
movie.poster = "http://image.tmdb.org/t/p/w500" + m.poster_path
movieList.push(movie)
// saveMovie(movie)
})
return Promise.all(movieList)
})
.then((movieList) => {
const deepMovieList = []
movieList.forEach((movie) => {
getMovieInfo(movie)
.then(function(response) {
movie.revenue = response.data.revenue
movie.budget = response.data.budget
movie.imdbId = response.data.imdb_id
deepMovieList.push(movie)
})
})
return Promise.all(deepMovieList)
})
.then((deepMovieList) => {
const finalMovies = []
deepMovieList.forEach((movie) => {
finalMovies.push(getYouTube(movie))
})
return Promise.all(finalMovies)
})
Everything works fine right up until the first mention of "deepMovieList". I can't seem to figure out how to have that particular step to work properly, as it essentially involves making 20 API calls with each movie in the movieList. I can't figure out how to 1) get the info back from the API, 2) assign the attributes to the movie object that is passed in to getMovieInfo, and then 3) push that movie object (with the new attributes) to an array that I can use Promise.all on, all without interrupting the promise chain.
Either it moves on to the next "then" function too early (while deepMovieList is still an empty array), or, with other random stuff I've tried, the array ends up being undefined.
How can I have the next "then" function wait until 20 API calls have been made and each movie object has its updated attributes? This will also run into the same problem in the next step, for the YouTube link.
Thanks!
TL;DR: use fetch and promises instead of jQuery, group promises using Promise.all.
The Longer Version
Ok, I'm not going to repeat all your code here. I'm going to abbreviate some stuff to keep it simple.
Basically, you have a bunch of tasks to perform. I'm going to pretend each of them is a function that returns a promise which is resolved with the data you want.
searchActor() - returns a promise resolved with some ID number
getMoviesFromActorID(actorId) - returns a promise that resolves with an array of movie IDs
getMovie(movieId) - returns a promise that resolves with the details for the given movie ID
getYoutube(movie) - returns a promise that resolves with the Youtube embed code.
Given this basic setup (and I admit I'm leaving out a lot of stuff), the code looks like this:
// search for an actor
searchActor('Brad Pitt')
// then get the movie IDs for that actor
.then((actorId) => getMoviesFromActorID(actorId))
// then iterate over the list of movie IDs & build an array of
// promises. Use Promise.all to create a new Promise which is
// resolved when all are resolved
.then((movieIdList) => {
const promiseList = [];
movieIdList.forEach((id) => promiseList.push(getMovie(id)));
return Promise.all(promiseList);
})
// then get Youtube links for each of the movies
.then((movieDetailsList) => {
const youtubeList = [];
movieDetailsList.forEach((movie) => youtubeList.push(getYoutube(movie)));
return Promise.all(youtubeList);
})
// then do something with all the information you've collected
.then((finalResults) => {
// do something interesting...
});
The key to this is Promise.all (documentation here), which will take an array of Promises (or any other iterable object containing promises) and create a new Promise which will resolve when all of the original promises have resolved. By using Promise.all, you can create a step in your promise chain which can include a variable number of parallel actions which must complete before the next step.
You could do something like this will jQuery and callbacks, but it would be pretty darn ugly. One of the great benefits of promises is the ability to lay out a series of steps like above.

How to manage multiple promises

I'm writing a crawler using node.js. At first, I need to fetch the main page to get the URL of each item on that page, then I crawl URL of each item to get details of them one-by-one
fetchPage(url) is to get HTML text of a link
function fetchPage(url){
return new Promise(
(resolve,reject)=>{
agent
.get(url)
.end(function(err,res){
if (err){
reject(err);
} else{
resolve(res.text);
}
});
});
}
This is the global call of this crawler
fetchPage(link).then(
(result)=>{
const urls=getUrls(result);
for (var i=0;i<5;i++){
fetchItem(urls[i].link).then(
(result)=>{
console.log('Done');
},
(error)=>console.log(error)
);
}
},
(error)=>console.log(error)
);
I processed to get the URLs of all items after fetching the main page (via getUrls)
fetchItem(url) is another Promise that ensures every HTML text of an item should be processed via getItem after being fetched by fetchPage
function fetchItem(url){
return new Promise(
(resolve,reject)=>{
fetchPage(url).then(
(result)=>{
getItem(result);
},
(error)=>reject(error)
);
});
}
It does crawl. It does get all items I need without any lack of information.
But there's something wrong with my code. Why doesn't the console log Done message for me?
Results are not in the right order. The order of crawled results is not as I expected, it is different from the order on the website.
Please point out what have I misunderstood and done wrong with these asynchronous control? How to ensure the order of them? How to fix this code to meet?
How should I do if I want to log a message All done after all items are completely crawled, making sure they're completely fetched in correct order?
Done is not getting called because you are not resolving the Promise created in fetchItem function.
I guess to maintain the order of results, you might want to use Promise.all. It will also help in getting All done message when all items are completely crawled.
I will start with changing fetchPage function by converting urls to a array of fetchItem promises using map that I can pass to Promise.all. Something like this
fetchPage(link).then(
(result)=>{
const urls=getUrls(result);
var promises = urls.map((url) => fetchItem(url.link));
Promise.all(promises).then((values) => {
console.log('All done');
console.log(values);
}, (error) => {
console.log(error);
});
},
(error)=>console.log(error)
);
then adding resolve to your fetchItem method.
function fetchItem(url){
return new Promise(
(resolve,reject)=>{
fetchPage(url).then(
(result)=>{
resolve(getItem(result));
},
(error)=>reject(error)
);
});
}

Firebase promise returns before collection is updated

I'm using AngularFire. I have some code which is supposed to add a new record to an array of records and using the promise then function, it is supposed to re-evaluate the array to find out which one has the most recent datestamp in the collection.
this.addRecord = function() {
// Add a new record with the value the user has typed in
$scope.records.$add({
"date": (new Date()).toString(),
"value": $scope.newValue
}).then(function( ref ) {
// Use underscore.last to determine which is the
var _newestValue = _.max( $scope.records, function(record) {
return record.date;
})[0].value;
sync.$update({ 'newestValue': _newestValue });
});
// Have angular clear the field
$scope.newValue = '';
}
The problem is that when the promise.then() calls, my local copy of $scope.records is not yet updated with the newest record. So while firebase out on the server now has the new record, when I iterate on $scope.records I get all the records except for the one I just added. After the .then() completes, I can see that the record has been added.
Maybe I'm using the promise wrong? I was under the impression that when AngularFire finally calls the .then() that it would be after angular had added the new record on the server and synced up the local collection.
What's the right way to do this? I just need to reliably know when the record has been added locally. Thanks in advance!
So it turns out using model.$watch was the right way to go. $watch only fires when synced changes are made to the model, so you know you can reliably count on them.
$scope.records.$watch( watchCallback );
watchCallback = function() {
if($scope.loadingModel) return; //Block updates during load
var _newestValue = _.max( $scope.records, function(record) {
return record.date;
})[0].value;
sync.$update({ 'newestValue': _newestValue });
}
AngularFire's model.$add().then() will fire when the changes were sent to the server, not when the local model is synced up to the client. So $add.then would be more appropriately used to confirm that changes were saved, or something along those lines.

how to manually slow down asynchronous javascript requests to parse.com in order to stay below 30 requests per second?

I'm fetching a collection called logCollection from parse.com in a node JS script on my machine. It has 200 elements. Each log has a link to another table (a pointer) called start. I need to fetch this one too.
Here is my code
Parse.User.logIn("user", "pass").then(function(user) {
// Do stuff after successful login.
console.log('succesfully logged in');
return logCollection.fetch();
}).then(function(content) {
console.log('done fetching logs: ' + logCollection.length);
var promises = [];
_.each(logCollection.models, function(thisLog) {
promises.push(thisLog.attributes.start.fetch());
});
// Return a new promise that is resolved when all of the deletes are finished.
return Parse.Promise.when(promises);
});
The thing is, it will fire at least 200 (start) fetch per second, and it will cause problems with the 30 requests per second limit at parse.com.
Is there a better way to do this? How can I slow down the way js fires the requests?
thanks
In a Parse Query, you can get the fully-fetched objects which are pointed to by that object, by using the include method on the query:
var query = new Parse.Query("SomeClass");
query.include('columnName');
query.find().then(function(results) {
// each result will have 'columnName' as a fully fetched parse object.
});
This also works with sub-sub objects:
query.include('columnName.nestedColumnName');
or as an array:
query.include(['columnName', 'anotherPointerColumn']);
I came out with this solution that works very good. It was all this time on the parse documentation.
https://www.parse.com/docs/js_guide#promises-series
The following code will fire one request only after the last one has been finished. Doing so, I can fire many requests without worrying about getting to the limit.
var query = new Parse.Query("Comments");
query.equalTo("post", 123);
query.find().then(function(results) {
// Create a trivial resolved promise as a base case.
var promise = Parse.Promise.as();
_.each(results, function(result) {
// For each item, extend the promise with a function to delete it.
promise = promise.then(function() {
// Return a promise that will be resolved when the delete is finished.
return result.destroy();
});
});
return promise;
}).then(function() {
// Every comment was deleted.
});

Categories

Resources