Angular construct ViewModel from different promises - javascript

In my Angular project i have a Promise that returns an array of Products:
{"Products":[{"Code":123},{"Code":456}]}
Then for each product code, i must call two other promises that return Price and Quantity respectively.
I use ui-router and my current implementation code in my resolve is below :
$stateProvider.state('root.products', {
abstract: true,
url: '/products',
template: '<div data-ui-view=""></div>',
resolve: {
products: ['ProductsService', function (ProductsService) {
return ProductsService.getProducts()
.then(function (response){
var data = response.data;
return data.Products.map(function (product) {
var viewModelProduct = {};
angular.copy(product, viewModelProduct);
//get Price for Each Product
ProductsService.getPrice(product.Code)
.then(function (response) {
viewModelProduct.Price = response.data.Info.Price;
})
//get Quantity for Each Product
ProductsService.getQuantity(product.Code)
.then(function (response) {
viewModelProduct.Quantity = response.data.Info.Quantity;
})
return viewModelProduct;
});
});
}]
}
})
It works but my question is if i can write it better. I read about the use of $q and $q.all but I don't really know how to use them.
Is there any way i can write the above code, better and safer??
Thanks in advance for your help!

It works but my question is if i can write it better.
Yes. You can write promise chain. The advantage is: Error propagates, you can catch it on the end of the chain and its not piramide like async call in Javascript
See flow example bellow:
[EDIT]
if the order is not critical you can use $q.all that gets list of promises and notify you when all of them resolved successfully.
It should be something like:
$q.all([
ProductsService.getPrice(product.Code),
ProductsService.getQuantity(product.Code)
])
.then(function(result){
viewModelProduct.Price = result[0].data.Info.Price;
iewModelProduct.Quantity = result[1].data.Info.Quantity;
}, function(error) {
alert(error.message);
});
or even:
var promises = [];
promises.push(ProductsService.getPrice(product.Code));
promises.push(ProductsService.getQuantity(product.Code));
$q.all(promises).then(function(result){
viewModelProduct.Price = result[0].data.Info.Price;
iewModelProduct.Quantity = result[1].data.Info.Quantity;
}, function(error) {
alert(error.message);
});

If you need to execute promises one after other you will use chains like Maxim mentioned.
More one topic:
https://docs.angularjs.org/api/ng/service/$q
http://solutionoptimist.com/2013/12/27/javascript-promise-chains-2/
If you need to execute more promises and then resolve it you will use "$q.all".
See documenttion:
https://docs.angularjs.org/api/ng/service/$q
UPDATE
Not sure if I understand well but you should use combination of q.all and chaining.
Again, sorry if I didn't understand you well.
var p1 = ProductsService.getPrice(product.Code)
.then(function (response) {
return response.data.Info.Price;
});
var p2 = ProductsService.getQuantity(product.Code)
.then(function (response) {
return response.data.Info.Quantity;
});
return $q.all([p1,p2]); // next "then" will get array with price and quantity respectively.

Related

Need help understanding the scope of this javascript variable

In javascript, within a VueJS SPA, I'm trying to create a method that will allow me to reduce redundant code by passing the Google Maps Places Service only a place_id and the fields I would like returned.
getPlaceDetails (place, fields) {
this.$refs.mapRef.$mapPromise.then((map) => {
var placesServices = new window.google.maps.places.PlacesService(map)
placesServices.getDetails({ placeId: String(place.place_id), fields: fields }, (result, status) => {
if (status === window.google.maps.places.PlacesServiceStatus.OK) {
alert(JSON.stringify(result))
return result
}
})
})
}
I'm calling the above method from within another method:
var place = this.getPlaceDetails(place, ['name', 'geometry', 'place_id'])
It is invoked successfully... and the alert shows the desired JSON.. but place is null. I've tried using
var vm = this
above
var placesServices
and assigning the result to an app level variable... even inside of a .then after the first promise... like so:
getPlaceDetails (place, fields) {
this.$refs.mapRef.$mapPromise.then((map) => {
var vm = this
var placesServices = new window.google.maps.places.PlacesService(map)
placesServices.getDetails({ placeId: String(place.place_id), fields: fields }, (result, status) => {
if (status === window.google.maps.places.PlacesServiceStatus.OK) {
alert(JSON.stringify(result))
vm.tempPlace = result
}
})
}).then(function () {
return this.tempPlace
})
}
How can I get the method to return the result object??
Promises
A promise is an object that will resolve (or reject) in some point in the future. This is necessary to execute asynchronous tasks (e.g. http-calls) that take an undefined amount of time to finish.
Promises can be chained i.e. get executed one after another. This is what the .then method does. With .then you pass a function that will be executed as soon as the promise is finished. This function will receive the object that was returned by the previous promise.
Your method
getPlaceDetails (place, fields) {
return this.$refs.mapRef.$mapPromise.then((map) => {
var vm = this;
var placesServices = new window.google.maps.places.PlacesService(map);
placesServices.getDetails({ placeId: String(place.place_id), fields: fields }, (result, status) => {
if (status === window.google.maps.places.PlacesServiceStatus.OK) {
alert(JSON.stringify(result));
return result;
}
});
});
}
This Method will return a promise that - at some point in the future - will yield the desired result.
When you want to call the method you get that promise and have to handle it, again by passing a function (using .then) that will be executed once the result is ready.
this.getPlaceDetails(...).then((result) => {
// handle your result
}}
Alternatively you could use the await operator to wait until the promise is finished:
var place = await this.getPlaceDetails(...);
Instead of returning the data, you may consider assigning the JSON to a Vue watched data variable, like so:
var somePlace = new Vue({
el: '#someEl',
data: {
message: 'Hello robwolf.io',
tempPlace: {} // <- variable waiting for your asynchronous data if it comes thru
},
methods: {
getPlaceDetails (place, fields) {
// [...your promise code here...]
}).then((result) => {
this.tempPlace = JSON.stringify(result)
// return this.tempPlace
})
// [...the .then function must also be an arrow function to have scope access to tempPlace...]

Multiple Mongoose Calls Within a For Each Loop

I am reading a JSON object and looping through each item. I am first checking to see if the item already exists in the database and if so I want to log a message. If it doesn't already exist I want to add it.
This is working correctly however, I would like to add a callback or finish the process with process.exit();
Because the mongoose calls are asynchronous I can't put it at the end of the for loop because they haven't finished.
Whats the best way I should handle this?
function storeBeer(data) {
data.forEach((beer) => {
let beerModel = new Beer({
beer_id: beer.id,
name: beer.name,
image_url: beer.image_url
});
Beer.findOne({
'name': beer.name
}).then(function (result) {
if (result) {
console.log(`Duplicate ${result.name}`)
} else {
beerModel.save(function (err, result) {
console.log(`Saved: ${result.name}`)
});
}
});
});
}
Is there anything I should read up on to help solve this?
One means of managing asynchronous resources is through Promise objects, which are first-class objects in Javascript. This means that they can be organized in Arrays and operated on like any other object. So, assuming you want to store these beers in parallel like the question implies, you can do something like the following:
function storeBeer(data) {
// This creates an array of Promise objects, which can be
// executed in parallel.
const promises = data.map((beer) => {
let beerModel = new Beer({
beer_id: beer.id,
name: beer.name,
image_url: beer.image_url
});
return Beer.findOne({
'name': beer.name
}).then(function (result) {
if (result) {
console.log(`Duplicate ${result.name}`)
} else {
beerModel.save(function (err, result) {
console.log(`Saved: ${result.name}`)
});
}
});
);
});
return Promise.all(promises);
}
Don't forget that the storeBeer function is now asynchronous, and will need to be utilized either through a Promise chain or through async/await.
For example, to add process exit afterwards:
async function main() {
const beers = [ ... ];
await storeBeer(beer);
process.exit(0);
}
You can also modify the above example to invoke the storeBeer function within a try / catch block to exit with a different error code based on any thrown errors.

async js validation form

Good morning !
Currently I am trying to create some kind of simple validation using javascript, but I have a problem with using async functionalities.
Below you can see UI method which iterates through validityChecks collections
checkValidity: function(input){
for(let i = 0; i<this.validityChecks.length; i++){
var isInvalid = this.validityChecks[i].isInvalid(input);//promise is returned
if(isInvalid){
this.addInvalidity(this.validityChecks[i].invalidityMessage);
}
var requirementElement = this.validityChecks[i].element;
if(requirementElement){
if(isInvalid){
requirementElement.classList.add('invalid');
requirementElement.classList.remove('valid');
}else{
requirementElement.classList.remove('invalid');
requirementElement.classList.add('valid');
}
}
}
}
Below is specific collection object which is not working as it was intended
var usernameValidityChecks = [
{
isInvalid: function(input){
var unwantedSigns = input.value.match(/[^a-zA-Z0-9]/g);
return unwantedSigns ? true:false;
},
invalidityMessage:'Only letters and numbers are allowed',
element: document.querySelector('.username-registration li:nth-child(2)')
},
{
isInvalid: async function (input){
return await checkUsername(input.value);
},
invalidityMessage: 'This value needs to be unique',
element: document.querySelector('.username-registration li:nth-child(3)')
}
]
function checkUsername (username) {
return new Promise ((resolve, reject) =>{
$.ajax({
url: "check.php",
type: "POST",
data: { user_name: username },
success: (data, statusText, jqXHR) => {
resolve(data);
},
error: (jqXHR, textStatus, errorThrown) => {
reject(errorThrown);
}
});
});
}
The problem is that to var isInvalid in checkValidity method is returned promise. Can anyone advice how to returned there value instead of promise ? Or how to handle promise there ?
Thank you in advance!
EDIT :
Forgive me, but it is my first time with Stack and probably my question is a little bit inaccurate. Some of objects in collections are also synchronous, I changed usernameValidityChecks. How to handle this kind of situation ? Where I have async and sync method at the same time ?
Instead of below line
var isInvalid = this.validityChecks[i].isInvalid(input);
you can use below code to make code much cleaner and store the desired value on the variable however above suggested solutions are also correct
var isInvalid = await this.validityChecks[i].isInvalid(input).catch((err) => { console.log(err) });
After this line you will get the desired value on isInvalid variable
And also add async keyword in all other isInvalid function definition
Use .then to access the result of a promise:
var isInvalid = this.validityChecks[i].isInvalid(input).then(res => res);

Multiple nested AJAX requests with arrays

I'm using Axios / promises to make AJAX requests, but the structure is a bit confusing. Basically I want this to happen:
Get masterlist.xml.
Parse the master list to get an array of category list URLs.
Get each category.xml.
Parse each category list to get an array of manifest URLs.
Get each manifest.xml and parse data.
Example structure:
masterlist.xml
<master>
<category>action</category>
<category>romance</category>
</master>
Category XMLs (e.g. action.xml and romance.xml)
<manifestUrls>
<manifest>foo/bar/manifest.xml</manifest>
<manifest>alice/bob/manifest.xml</manifest>
<manifest>hello/world/manifest.xml</manifest>
</manifestUrls>
Manifest XMLs
<manifest>
<data>Blah blah blah</data>
<manifest>
I'm a bit stuck on how to structure this as an Axios request with then. I'd like to have three functions, getMaster(), getCategory(url), and getManifest(url) that are preferably independent (e.g. getMaster doesn't have to call getCategory directly), but it seems like it might be necessary.
How would this be structured in Axios?
One of the main benefits of promises is that they allow you to easily avoid interdependence between your methods.
Here is a rough outline of how you could do this.
// put it all together
getMaster()
.then(parseMaster)
.then(function (categories) {
return Promise.all(categories.map(getAndParseCategory));
})
.then(flatten) // the previous then() resolves to an array-of-arrays
.then(function (manifestUrls) {
return Promise.all(manifestUrls.map(getManifest));
})
.then(function (manifests) {
// manifests is an array of all manifests
});
// Examples of what each of the functions used above would do
function getMaster() {
return axios.get('masterUrl')
.then(function (response) { return response.data; });
}
function parseMaster(masterContent) {
// parse and return an array of categories
}
function getCategory(name) {
var url = // ... build the URL based on the name
return axios.get(url)
.then(function (response) { return response.data; });
}
function parseCategory(categoryContent) {
// parse and return an array of URLs synchronously for one category
}
function getAndParseCategory(name) {
return getCategory(name).then(parseCategory);
}
function getManifest(url) {
return axios.get(url)
.then(function (response) { return response.data; });
}
function flatten(arrayOfArrays) {
return [].concat.apply([], arrayOfArrays);
}
If you're using Bluebird or something else that gives promises a .map() method, then you can tidy that pipeline up a bit:
// using Promise.resolve() at the beginning to ensure
// the chain is based of the desired kind of promise
Promise.resolve()
.then(getMaster)
.then(parseMaster)
.map(getCategory)
.map(parseCategory)
.then(flatten) // previous line resolves to an array-of-arrays
.map(getManifest)
.then(function (manifests) {
// manifests is an array of all manifests
});
Of course, you could also define your own .map method if you don't want to import a whole third party promise library:
if (!Promise.prototype.map) {
Promise.prototype.map = function (func) {
return this.then(function (result) {
return Promise.all(result.map(func));
});
};
}
Edit: To respond to your question in the comments below. If you wanted to pass the category text along so that it could be included in the manifest URLs, I think a clean way to do this would be to include that in the data returned from getCategory() so that parseCategory can make use of it. Everything else could stay the same.
Example:
function getCategory(name) {
var url = // ... build the URL based on the name
return axios.get(url)
.then(function (response) {
return {
name: name,
data: response.data
};
});
}
function parseCategory(categoryContent) {
var urls = // parse URLs from categoryContent.data
return urls.map(function (url) {
return categoryContent.name + '/' + url;
});
}

Synchronously using forEach with $q

So I have a situation where I need to build an Object, then inject some items into that Object, and then make an API call and also inject the API's response into that Object. It's a complex Object so we'll just assume that's alright, but anyway, I think that I need to do these 3 things synchronously and using array.forEach means they run asynchronously.
I hope my example is simple enough to understand, but basically I do 3 things:
Create an empty Array of Classes/Classrooms
Loop through an Array of Class IDs and create an Object for each Class
Loop through an Array of Students and push them into a students Array inside the Class Object
Loop through an Array of Options and push them into an options array inside the Class Object
Finally, I have something that could look like this for each Class:
{
class_id: "abc123",
students: [{}, {}, {}],
options: [{}, {}, {}]
}
And finally, here is my code:
// Create Array of Class Objects
var classes = [];
function processArray(array, func) {
return $q(function(resolve, reject) {
array.forEach(function(item, index) {
func(item);
if (index === (array.length - 1)) resolve();
})
})
}
// Create Courier Objects
processArray(classIds, function(id) {
classes.push({class_id: id, students: [], options: []});
}).then(function(response) {
// Inject Students into each Class
processArray(students, function(students) {
_.find(classes, {'class_id': student.class_id}).students.push(student);
}).then(function(response) {
// Inject classOptions into each Class
processArray(classOptions, function(classOption) {
_.find(classes, {'class_id': classOption.class_id}).classOptions.push(classOption);
}).then(function(response) {
// Print the classes
console.log(classes);
})
})
});
I've create a function which does it synchronously but I'd like to know if anyone can think of a much, much cleaner and more efficient way of doing the above. It seems extremely hacky, and maybe I don't even need to do it synchronously if I arranged my functions correctly.
Working with Promise Based APIs that Return Arrays
The processArray function returns a promise that resolves to null.
//WRONG
//Returns a promise that resolves to null
//
function processArray(array, func) {
return $q(function(resolve, reject) {
array.forEach(function(item, index) {
func(item);
if (index === (array.length - 1)) resolve();
})
})
}
To return a promise that resolves to an array, use $q.all.
//RIGHT
//Returns a promise that resolves to an array
//
function processArray(array, func) {
var promiseArray = [];
array.forEach(function(item, index) {
promiseArray.push($q.when(func(item));
});
return $q.all(promiseArray);
}
In either case whether func(item) returns either a value or a promise, $q.when will return a promise.
Be aware that $q.all is not resilient. It will resolve fulfilled with an array of values or it will resolve rejected with the first error.
processArray(array, func)
.then( function onFulfilled(dataList) {
//resolves with an array
$scope.dataList = dataList;
}).catch( function onRejected(firstError) {
console.log(firstError);
});
For more information, see AngularJS $q Service API Reference.
you are using nested promises. These suck. One major advantage of promises is to avoid the 'callback nightmare' of deep nesting.
Use this syntax ...
promise1.then(function(response1) {
// do something with response 1 (and possibly create promise2 here based on response1 if required)
return promise2
}).then(function(response2) {
// do something with response 2
return promise3
}).then(function(response3) {
// do something with response 3
// do something with promise 3???
}).catch(function(errorResponse) {
// will trigger this on a failure in any of the above blocks
// do something with error Response
});

Categories

Resources