Even though I have managed to make my code work, there is something I don't understand. The following piece of code functions correctly:
socket.on('method', function() {
var payload = {
countrycode: '',
device: ''
};
var d1 = $q.defer();
var d2 = $q.defer();
$q.all([
geolocation.getLocation().then(function(position) {
geolocation.getCountryCode(position).then(function(countryCode){
payload.countrycode = countryCode;
d1.resolve(countryCode);
});
return d1.promise;
}),
useragent.getUserAgent().then(function(ua) {
useragent.getIcon(ua).then(function(device) {
payload.device = device;
d2.resolve(device);
});
return d2.promise
})
]).then(function(data){
console.log(data); //displays ['value1', 'value2']
})
});
Is there a better way of achieving this? Before I had only one deferred variable, i.e. varvar deferred = $q.defer(); but that way the .then() function returned an object with double the results.
So the few question I have are:
Do I need multiple $q.defer vars?
Is the above the best way to wait for two async calls to finish and populate the payload object?
socket.on('method', function() {
var payload = {
countrycode: '',
device: ''
};
geolocation.getLocation()
.then(function(position) {
return geolocation.getCountryCode(position);
})
.then(function(countryCode) {
payload.countrycode = countryCode;
return useragent.getUserAgent();
})
.then(function(ua) {
return useragent.getIcon(ua);
})
.then(function(device) {
payload.device = device;
console.log(data); //displays ['value1', 'value2']
});
});
read the promise chaining part
You could always separate your code into smaller semantic blocks like so:
getCountryCode = function() {
var d = $q.defer();
geolocation.getLocation()
.then(function(position) {
return geolocation.getCountryCode(position)
})
.then(function(countryCode) {
d.resolve(countryCode);
})
.fail(function(err) {
d.reject(err);
})
return d.promise;
};
getDevice = function() {
var d = $q.defer();
useragent.getUserAgent()
.then(function(ua) {
return useragent.getIcon(ua)
})
.then(function(device) {
d.resolve(device);
})
.fail(function(err) {
d.reject(err);
});
return d.promise;
}
That will shorten your actual parallel call ($q.all) quite a bit:
socket.on('method', function() {
$q.all([getCountryCode(), getDevice()])
.spread(function(countryCode, device) {
var payload = {
countryCode: countryCode,
device: device
};
// ... do something with that payload ...
});
});
To synchronize multiple asynchronous functions and avoid Javascript callback hell:
http://fdietz.github.io/recipes-with-angular-js/consuming-external-services/deferred-and-promise.html
Related
I am trying to update taxParentId with the new id that i retrieve with my API call inside the getTaxParentId function, but I cannot get it to change. I can console.log the value fine inside the method, but it won't update it. It seems to be an issue of scope, but i have set $this = this to take care of this, however, it is not working.
the getPostType method works fine and properly updates the data value.
var newVue = new Vue({
el: '#app',
data() {
return{
posts: [],
taxonomy: '',
postType: '',
taxParentSlug: '',
taxParentId: 0
}
},
created (){
let $this = this;
this.getPostType(location.href);
this.getTaxParent(location.href)
this.getTaxParentId();
this.getPosts();
},
methods: {
getPostType: function(currentURL){
if (currentURL.includes('residential')) {
this.postType = 'residential';
}else if(currentURL.includes('commercial')){
this.postType = 'commercial';
}else if (currentURL.includes('auto')) {
this.postType = 'auto';
}
},
getTaxParent: function(currentURL){
if (currentURL.includes('solar')) {
this.taxParentSlug = 'solar';
}else if(currentURL.includes('decorative')){
this.taxParentSlug = 'decorative';
}else if (currentURL.includes('safety-security')) {
this.taxParentSlug = 'safety-security';
}
},
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
}
)
},
getPosts: function(){
let $this = this;
console.log(apiRoot + $this.postType + '-categories?parent=' + $this.taxParentId)
axios
.get(apiRoot + $this.postType + '-categories?parent=' + $this.taxParentId)
.then(function (response) {
$this.posts = response.data;
console.log($this.posts)
}
)
},
},
});
Because of the async, add watchers to your data, and log there.
watch:{
posts(value){console.log(value))},
taxParentId(value){console.log(value))}
}
Ideally you would get a promise from each call, and then wait for them all. If one call is dependent on another, you need to put the second call in a then() block, or even better, await it (async/await)
Using this, all you need to do is return the promise, and it will be synchronized.
async created (){
let $this = this;
await this.getPostType(location.href);
await this.getTaxParent(location.href)
await this.getTaxParentId();
await this.getPosts();
},
So much cleaner then chaining then blocks. You can wrap the entire block in a SINGLE catch, and trap all exceptions AND all rejections. Of course, if the calls are not dependent, you may want to call them in parallel and not await.
Since you are already using promises, you should be able to build a promise chain to solve your async issue.
Take your current function:
```javascript
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
}
)
},
and make it return a value, even if it is just the response
```javascript
getTaxParentId: function(){
let $this = this;
axios
.get(apiRoot + $this.postType + '-categories')
.then(function (response) {
response.data.forEach(function(item){
if (item.slug == $this.taxParentSlug) {
$this.taxParentId = item.id;
}
});
return response
}
)
},
Then in your created() function, you can chain the call..
created (){
let $this = this;
this.getPostType(location.href);
this.getTaxParent(location.href)
this.getTaxParentId()
.then(function (response) {
this.getPosts();
})
},
This should force this.getPosts() to wait for getTaxParentId to be complete.
I've search for a sollution but I didn't find something like that.
I'm using angular, I want to call a function inside another function, and wait for its response.
the 2nd function is:
self.changeProvider = function() {
var contexec = false;
if (!checkIsFit()) {
contexec = true;
} else {
contexec = false;
}
if (contexec) {
var modalOptions = {
closeButtonText: $translate.instant('closeButtonText'),
actionButtonText: $translate.instant('ok'),
headerText: $translate.instant('changeProvidertitle'),
bodyTemplate: '../themes/default/src/app/shoppingCart/changeProvider/changeProvider.tpl.html',
margin: true
};
var modalDefaults = {
backdrop: 'static',
templateUrl: '../themes/default/src/app/shoppingCart/changeProvider/changeProvider.tpl.html',
controller: 'ChangeProviderCtrl',
size: 'sm',
resolve: {
modalData: function() {
return {
data: $scope.arrayToChangeProvider
};
}
}
};
modalService.showModal(modalDefaults, modalOptions)
.then(function(result) {
//some stuff
});
}
};
And the other function:
var checkIsFit = function() {
if ( $scope.cabstatus != 4 ) {
return false;
} else {
var modalOptions = {
closeButtonText: $translate.instant('closeButtonText'),
actionButtonText: $translate.instant('ok'),
headerText: $translate.instant('cabisfittedtitle'),
bodyTemplate: '../themes/default/src/app/shoppingCart/checkIsFit/checkIsFit.tpl.html',
margin: true
};
var modalDefaults = {
backdrop: 'static',
templateUrl: '../themes/default/src/app/shoppingCart/checkIsFit/checkIsFit.tpl.html',
controller: 'CheckIsFitCtrl',
size: 'sm',
resolve: {
modalData: function() {
return {
};
}
}
};
modalService.showModal(modalDefaults, modalOptions)
.then(function(result) {
if (result.msg === 'ok') {
var params = {
token: $scope.token,
fkidpedido: $scope.pendingOrderLineList[0].FK_IDPEDIDO,
userid : $scope.userid
};
shoppingCartService.postResetAgr(params, function() {
return true;
}, function() {
/*Notification.error({
message: $translate.instant('components.activity.actions.deleteActivityError')
});*/
});
return false;
} else {
return true;
}
});
}
};
The problem is the function changeProvider still executing and opens the modal first to resolve the funcion checkIsFit()
I want to wait checkIsFit is resolved and then continue with the functions of changeProvider
I cannot include the checkIsFit() functionallity inside changeProvider because I want to use checkIsFit() into another functions.
Any help will be appreciate.
Thanks in advance
I believe what you're looking for are deferred objects and promises. Check out the documentation for $q:
https://docs.angularjs.org/api/ng/service/$q
I'd recommend giving this a good read because this is a really important and powerful concept for ANY Javascript developer.
At the essence, deferred objects and promises allow you run asynchronous processes and callback to a function when a process is complete.
The modalService.showmodal method returns a promise. Create functions that return those promises.
var modalPromise1Fn = function () {
var promise1 =
modalService.showModal(modalDefaults1, modalOptions2)
.then(function(result) {
//some stuff
});
return promise1;
};
var modalPromise2Fn = function () {
var promise2 =
modalService.showModal(modalDefaults2, modalOptions2)
.then(function(result) {
//some stuff
});
return promise2;
};
This use the .then method of the first promise to chain the second promise.
var derivedPromise =
modalPromise1Fn().then( function() {
var promise2 = modalPromise2Fn();
//return to chain the second promise
return promise2;
});
From the Docs:
Chaining promises
Because calling the .then method of a promise returns a new derived promise, it is easily possible to create a chain of promises.
It is possible to create chains of any length and since a promise can be resolved with another promise (which will defer its resolution further), it is possible to pause/defer resolution of the promises at any point in the chain. This makes it possible to implement powerful APIs .
-- AngularJS $q Service API Reference -- Chaining Promises
I am using Angular resourse to get my data from an API, in this way:
var getAccountListPerUser = function () {
return $resource(uri, {}, {
get: {
headers: service.getDefaultHeaderRequest(),
method: 'GET',
transformResponse: function (data) {
var accountList = [];
try {
accountList = JSON.parse(data);
} catch (e) {
accountList = [];
}
return accountList;
},
isArray: true,
cache: true
}
}).get().$promise;
};
In my controller I have to use it and another two service functions defined in the same way.
var promiseResourcesAccountList = usrWebUserService.getAccountListPerUser();
promiseResourcesAccountList.then(function(result){
$scope.usersWithAccountsAndProfiles = result;
var filteredProfiles = [];
for (var account in result) {
...
}
$scope.filteredProfiles = filteredProfiles;
});
And:
var promiseResourcesEditUser = usrWebUserService.getResourcesUser(currentUser);
promiseResourcesEditUser.then(function (result) {
usrWebUserFactory.mapBasicPreferences($scope, result);
});
And then another very similar, this information loads data in three divs, but I want to show them only when all the three functions have completed correctly. I think I have to chain the result of the promises. How can I do that?
You can chain them like:
promiseResourcesAccountList.then(function(result){
///whatever processing
//return a promise
return promiseResourcesEditUser()
}).then(function(){
return anotherPromise();
}).then(function(){
//update scope here
});
alternatively, you could also use $q.all([promise1, promise2, promise3]).then(...);
#terpinmd is correct. Chaining promises is pretty simple. Say you have a service with a "getWidgets" that returns a promise, and you want to use the response from that service to call another service, "getWidgetOwners" that will return another promise :
Assumptions
getWidgets returns an array of widget objects.
getWidgetOwners accepts an array of ownerIds
How To:
service.getWidgets()
.then(function(widgets) {
return widgets.map(function(widget) { // extract ownerIds
return widget.ownerId;
});
})
.then(service.getWidgetOwners) // pass array of ownerId's to
.then(function(owners) { // the next service
console.log(owners);
});
I have the following code (Mongoose has been promisified with Bluebird)
function createNewCourse(courseInfo, teacherName) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
return newCourse.saveAsync()
.then(function (savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
return newTeacher.saveAsync().then(function() {
return newCourse;
});
});
}
This is a simplification of my problem, but it illustrates it well. I want my createNewCourse function to return a promise that, once resolved, will return the newly saved course, not the teacher. The above code works, but it's not elegant and does not use promises well to avoid callback hell.
Another option I considered is returning the course and then doing a populate, but that would mean re-querying the database.
Any ideas how to simplify this?
Edit: I thought it might be useful to post the save code but using native callbacks (omitting error-handling)
function createNewCourse(courseInfo, teacherName, callback) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
newCourse.save(function(err, savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
newTeacher.save(function(err, savedTeacher) {
callback(null, newCourse);
});
});
}
Use .return():
function createNewCourse(courseInfo, teacherName) {
var newCourse = new courseModel({
cn: courseInfo.courseName,
cid: courseInfo.courseId
});
return newCourse.saveAsync().then(function (savedCourse) {
var newTeacher = new newTeacherModel({
na: teacherName,
_co: savedCourse._id // This would be an array
});
return newTeacher.saveAsync();
}).return(newCourse);
}
Remember how chaining works?
.then(function() {
return somethingAsync().then(function(val) {
...
})
})
Is equivalent to (disregarding any closure variables):
.then(function() {
return somethingAsync()
})
.then(function(val) {
...
})
return(x) is simply equivalent to .then(function(){return x;})
I think I'm writing my promise incorrectly and I couldn't figure out why it is caching data. What happens is that let's say I'm logged in as scott. When application starts, it will connect to an endpoint to grab listing of device names and device mapping. It works fine at this moment.
When I logout and I don't refresh the browser and I log in as a different user, the device names that scott retrieved on the same browser tab, it is seen by the newly logged in user. However, I can see from my Chrome's network tab that the endpoint got called and it received the correct listing of device names.
So I thought of adding destroyDeviceListing function in my factory hoping I'll be able to clear the values. This function gets called during logout. However, it didn't help. Below is my factory
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
var deferredLoad = $q.defer();
var isLoaded = deferredLoad.promise;
var _deviceCollection = { deviceIds : undefined };
isLoaded.then(function(data) {
_deviceCollection.deviceIds = data;
return _deviceCollection;
});
return {
destroyDeviceListing : function() {
_deviceCollection.deviceIds = undefined;
deferredLoad.resolve(_deviceCollection.deviceIds);
},
getDeviceIdListing : function() {
return isLoaded;
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
var deferred = $q.defer();
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
_deviceCollection.deviceIds = _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
}
})
.value();
deferredLoad.resolve(_deviceCollection.deviceIds);
var deviceIdMapping = _.chain(_deviceCollection.deviceIds)
.groupBy('deviceId')
.value();
deferred.resolve(deviceIdMapping);
});
return deferred.promise;
}
}
}])
and below is an extract from my controller, shortened and cleaned version
.controller('DeviceController', ['DeviceFactory'], function(DeviceFactory) {
var deviceIdMappingLoader = DeviceFactory.getDeviceIdMapping('http://10.5.1.7/v1');
deviceIdMappingLoader.then(function(res) {
$scope.deviceIdMapping = res;
var deviceIdListingLoader = DeviceFactory.getDeviceIdListing();
deviceIdListingLoader.then(function(data) {
$scope.deviceIDCollection = data;
})
})
})
Well, you've only got a single var deferredLoad per your whole application. As a promise does represent only one single asynchronous result, the deferred can also be resolved only once. You would need to create a new deferred for each request - although you shouldn't need to create a deferred at all, you can just use the promise that you already have.
If you don't want any caching, you should not have global deferredLoad, isLoaded and _deviceCollection variables in your module. Just do
app.factory('DeviceFactory', ['$q','User', 'DeviceAPI', function($q, User, DeviceAPI) {
function getDevices(deviceIdsEndpoint) {
var userData = User.getUserData();
// REST endpoint call using Restangular library
RestAPI.setBaseUrl(deviceIdsEndpoint);
RestAPI.setDefaultRequestParams( { userresourceid : userData.resourceId, tokenresourceid : userData.tokenResourceId, token: userData.bearerToken });
return RestAPI.one('devices').customGET('', { 'token' : userData.bearerToken })
.then(function(res) {
return _.chain(res)
.filter(function(data) {
return data.devPrefix != 'iphone'
})
.map(function(item) {
return {
devPrefix : item.devPrefix,
name : item.attributes[item.devPrefix + '.dyn.prop.name'].toUpperCase(),
};
})
.value();
});
}
return {
destroyDeviceListing : function() {
// no caching - nothing there to be destroyed
},
getDeviceIdListing : function(deviceIdsEndpoint) {
return getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
},
getDeviceIdMapping : function(deviceIdsEndpoint) {
return this.getDeviceIdListing(deviceIdsEndpoint)
.then(function(deviceIds) {
return _.chain(deviceIds)
.groupBy('deviceId')
.value();
});
}
};
}])
Now, to add caching you'd just create a global promise variable and store the promise there once the request is created:
var deviceCollectionPromise = null;
…
return {
destroyDeviceListing : function() {
// if nothing is cached:
if (!deviceCollectionPromise) return;
// the collection that is stored (or still fetched!)
deviceCollectionPromise.then(function(collection) {
// …is invalidated. Notice that mutating the result of a promise
// is a bad idea in general, but might be necessary here:
collection.deviceIds = undefined;
});
// empty the cache:
deviceCollectionPromise = null;
},
getDeviceIdListing : function(deviceIdsEndpoint) {
if (!deviceCollectionPromise)
deviceCollectionPromise = getDevices(deviceIdsEndpoint)
.then(function(data) {
return { deviceIds: data };
});
return deviceCollectionPromise;
},
…
};