I've read many answers to this question but I just don't get it. Where does the promise go? I made a simple factory with an async call to a cloud database:
app.factory('asyncFactory', function() {
let toController = function() {
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return snapshot.val(); // this returns later
});
return 47 // this returns immeadiately
};
return {
toController: toController // why is this necessary?
}
});
I call it from my controller:
$scope.words = asyncFactory.toController();
console.log($scope.words);
Here's the response:
As you can see, 47 returns to the controller immediately. If I comment out return 47 then the factory returns undefined. Later the async data logs but doesn't return to the controller. I use promises every day but I can't figure out where the promise goes.
Second question: do I need the line toController: toController ? Can I get rid of it?
Thanks!
To use the results from the firebase call in the controller, the factory method needs to return a promise:
app.factory('asyncFactory', function($q) {
return {
toController: toController
};
function toController() {
var es6promise = firebase.database().ref('en').once('value');
var qPromise = $q.when(es6promise)
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return snapshot.val(); // this returns later
});
return qPromise;
};
});
Because the firebase .once method returns an ES6 promise, that promise needs to be brought into the AngularJS framework by converting it to a $q Service promise with $q.when. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc.
In the controller, use the .then method to extract the data after it returns from the server:
var qPromise = asyncFactory.toController();
qPromise.then(function(data) {
console.log(data)
$scope.words = data;
});
The factory function immediately returns a promise. When the data arrives from the server, the data will be placed on $scope.
Well toController is eating the promise for itself. ( whenever you call .then(), it means you are waiting for promise),
Try this
app.factory('asyncFactory', function() {
let toController = function() {
var deferred = $q.defer();
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return deferred.resolve(snapshot.val()); // this returns later
});
//return deferred.resolve(47) // this returns immeadiately
};
return {
toController: toController // why is this necessary?
}
});
If you don't want this line
return {
toController: toController // why is this necessary? }
app.factory('asyncFactory', function() {
return {
var deferred = $q.defer();
firebase.database().ref('en').once('value') // get the array from the cloud database
.then(function(snapshot) { // take a snapshot
console.log(snapshot.val()); // read the values from the snapshot
return deferred.resolve(snapshot.val()); // this returns later
});
//return deferred.resolve(47) // this returns immeadiately
};
})
Related
I have one Angular.js method from child controller, where it makes a call to twop parent controller methods one after another. But the second function should get the account data from from the first function and then will update call data like below:
filter.filterAccountsByProductMetrics1 = function(productWithSegmentations12) {
accountService.fetchAccountForRecordType([filter.selectedHcpHco.Name.display])
.then(function(resp) {
$scope.accountDataUpdate({
accounts: resp.data
});
var productId = null;
if(filter.selectedMySetupProduct.Product_vod__c) {
productId = filter.selectedMySetupProduct.Product_vod__c.value;
}
callService.getCallsForProductId(productId)
.then(function(calls) {
filter.filterRecords[filterType.product.value] = calls;
$scope.callDataUpdate({
calls: applyAllFilterOnCalls()
});
});
});
};
I've checked both the functions are getting called but the sequence is not maintained. How to make sure the two parent functions get called one after another.
EDIT: function accountDataUpdate:
call.accountDataUpdate = function(accounts) {
call.accounts = accounts;
getCallDetails(getCallIdsFromCallsForFilteredAccount())
.then(function() {
updateProductFrequencyTableData();
updateAccountDetailData(true);
});
updateDailyFrequencyChartData();
updateWeeklyFrequencyChartData();
updateCallFrequencyTableData();
updateAccountFrequencyData();
$timeout(function() {
$scope.$broadcast('updateDoughnutChart');
$scope.$broadcast('updateBarChart');
});
};
Modify accountDataUpdate to return a promise:
call.accountDataUpdate = function(accounts) {
call.accounts = accounts;
var promise = getCallDetails(getCallIdsFromCallsForFilteredAccount())
.then(function() {
updateProductFrequencyTableData();
updateAccountDetailData(true);
updateDailyFrequencyChartData();
updateWeeklyFrequencyChartData();
updateCallFrequencyTableData();
updateAccountFrequencyData();
return $timeout(function() {
$scope.$broadcast('updateDoughnutChart');
$scope.$broadcast('updateBarChart');
});
});
return promise;
};
Then use that promise for chaining:
filter.filterAccountsByProductMetrics1 = function(productWithSegmentations12) {
return accountService.fetchAccountForRecordType([filter.selectedHcpHco.Name.display])
.then(function(resp) {
return $scope.accountDataUpdate({
accounts: resp.data
});
}).then(function() {
var productId = null;
if(filter.selectedMySetupProduct.Product_vod__c) {
productId = filter.selectedMySetupProduct.Product_vod__c.value;
}
return callService.getCallsForProductId(productId)
}).then(function(calls) {
filter.filterRecords[filterType.product.value] = calls;
return $scope.callDataUpdate({
calls: applyAllFilterOnCalls()
});
});
};
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.
For more information, see AngularJS $q Service API Reference - Chaining promises.
I have about 5 requests that are made when my view loads. This is for an edit form:
var reqA = function() {...};
var reqB = function() {...};
var reqC = function() {...};
var reqD = function() {...};
var reqE = function() {...};
Now I want reqA() and reqB() to load asynchronously and if possible return a single promise.
reqC() and reqD() should only load after reqA() and reqB() have finished and executed their promise.
reqE() should only load after reqC() and reqD().
This is what I want to do, however I have no idea how to go about it. I can load them all asynchronously or one after another by chaining promises but not in the way I want.
If your functions all use the $http provider, it is easy to accomplish. You will want to modify your functions to return the result of the $http call like this
function reqA() {
return $http.get(...);
}
Now that they are returning promises, you can easily use the $q provider to orchestrate your requests:
$q.all([reqA(), reqB()])
.then( () =>
$q.all([reqC(), reqD()])
.then(() => reqE())
);
For regular JS (non ES6):
$q.all([reqA(), reqB()])
.then(function() {
$q.all([reqC, reqD()])
.then(function() {
reqE();
});
});
If you don't want to return the result of $http in your functions, you will need to set them up to return a promise in one way or the other:
function reqA() {
var deferred = $q.defer();
... your request code ...
// call this if everything is ok
deferred.resolve();
// call this if there was an error
deferred.reject();
return deferred.promise;
}
Here is an example that you can take a look at that might make it clearer. It makes use of Promise which is globally available in almost all javascript environments (excluding IE and older node versions).
angular
.module('example', [])
.run(function($q) {
function requestOne() {
return $q.when("one")
}
function requestTwo() {
return $q.when("two")
}
function requestThree() {
return $q.when("three")
}
function requestFour() {
return $q.when("four")
}
function requestFive() {
return $q.when("five")
}
$q
.all([requestOne(), requestTwo()])
.then(function(responses){
console.log(responses[0] === "one");
console.log(responses[1] === "two");
return $q.all([requestThree(), requestFour()]);
})
.then(function(nextResponses){
console.log(nextResponses[0] === "three");
console.log(nextResponses[1] === "four")
return requestFive();
})
.then(function(lastResponse){
console.log(lastResponse === "five")
})
});
angular.element(document).ready(function() {
angular.bootstrap(document, [ 'example' ]);
});
I assume that you are using angular 1, if you aren't there Promise is available in the global scope in almost all environments.
I have a series of services that either fetch data from an API server, or return data if it exists in local storage.
.factory('LogEntryResource', function(config, $resource) {
return $resource(config.apiUrl + 'logentries/:id/');
})
.factory('LogEntry', function(localStorageService, LogEntryResource) {
var localLogEntries = localStorageService.get("logEntries");
return {
all: function() {
if(localLogEntries){
return localStorageService.get("logEntries");
} else {
return LogEntry.query(function(data){
localStorageService.set("logEntries", data);
return data;
});
}
},
get: function(logEntryId){
...
},
delete: function(logEntryId){
...
},
update: function(logEntryId){
...
}
}
})
The problem is that in the app controllers sometimes a promise is returned, and sometimes the data is returned, so I need to handle the return value of LogEntry.all() to either wait for the promise to resolve or to use the data.
I'm not really sure how to go about it because I can either use a .then() which works for the promise, but is undefined if it has data, or vice-versa. I know I'm doing something wrong and looking for advice how to handle this situation of dealing with either data or a promise being returned.
.controller('LogEntryCtrl', function($scope, LogEntry) {
// var data = LogEntry.all();
// var promise = LogEntry.all();
$scope.logEntry = ???
}
I'm hoping there's a nice reusable solution instead of having to do a check to see what it is every time I use this code in my controllers/routes
// trying to avoid doing this
var logEntry = LogEntry.all();
if(logEntry.isPromise){
// do promise stuff here
} else if(logEntry.isData {
// do data stuff here
}
My suggestion would be always return a promise. You can use $q.resolve() to create a shortcut for a resolved promise
.factory('LogEntry', function(localStorageService, LogEntry, $q) {
var localLogEntries = localStorageService.get("logEntries");
return {
all: function() {
if(localLogEntries){
return $q.resolve(localLogEntries);
} else {
return LogEntry.query(function(data){
localStorageService.set("logEntries", data);
// update variable also
localLogEntries = data;
return localLogEntries ;
});
}
},
In controller you always use then() this way
LogEntry.all().then(function(data){
$scope.data = data;
});
I have inherited an angular app and now need to make a change.
As part of this change, some data needs to be set in one controller and then used from another. So I created a service and had one controller write data into it and one controller read data out of it.
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
})
.controller('pageSubController', function (myService) {
// Read data from service
var data = myService.getData();
// Do something with data....
})
However, when I go to use data in pageSubController it is always undefined.
How can I make sure that pageController executes before pageSubController? Or is that even the right question to ask?
EDIT
My service code:
angular.module('appRoot.factories')
.factory('myService', function () {
var data = [];
var addData = function (d) {
data = d;
};
var getData = function () {
return data;
};
return {
addData: addData,
getData: getData
};
})
If you want your controller to wait untill you get a response from the other controller. You can try using $broadcast option in angularjs.
In the pagecontroller, you have to broadcast your message "dataAdded" and in the pagesubcontroller you have to wait for the message using $scope.$on and then process "getData" function.
You can try something like this :
angular.module('appRoot.controllers')
.controller('pageController', function (myApiService, myService,$rootScope) {
// load data from API call
var data = myApiService.getData();
// Write data into service
myService.addData(data);
$rootScope.$broadcast('dataAdded', data);
})
.controller('pageSubController', function (myService,$rootScope) {
// Read data from service
$scope.$on('dataAdded', function(event, data) {
var data = myService.getData();
}
// Do something with data....
})
I would change your service to return a promise for the data. When asked, if the data has not been set, just return the promise. Later when the other controller sets the data, resolve the previous promises with the data. I've used this pattern to handle caching API results in a way such that the controllers don't know or care whether I fetched data from the API or just returned cached data. Something similar to this, although you may need to keep an array of pending promises that need to be resolved when the data does actually get set.
function MyService($http, $q, $timeout) {
var factory = {};
factory.get = function getItem(itemId) {
if (!itemId) {
throw new Error('itemId is required for MyService.get');
}
var deferred = $q.defer();
if (factory.item && factory.item._id === itemId) {
$timeout(function () {
deferred.resolve(factory.item);
}, 0);
} else {
$http.get('/api/items/' + itemId).then(function (resp) {
factory.item = resp.data;
deferred.resolve(factory.item);
});
}
return deferred.promise;
};
return factory;
}
I have a wcf service method that gets some data and I call it using Microsoft Ajax library.
To share this data I create a dataService, and many controllers use this service.
I want every controller to get the same data after first call of getData is done, unless somebody need to refresh data and set forceRefresh to true.
My code fails because with the initialize of application 3 controler call dataService.getData and for all there start a new request. How can I make wait dataService.getData calls until the first one is finished and get same result for other subsequent ones..
angular.module('app', []).factory('dataService', function ($q) {
var data= [];
var getData= function (forceRefresh) {
console.log('getFolders called: ', reports.length);
var deferred = $q.defer();
if (forceRefresh || data.length < 1) {
WcfService.GetData(function(result) {
data= result;
deferred.resolve(data);
}, function(ex) { console.log(ex); });
} else {
deferred.resolve(reports);
}
return deferred.promise;
};
return {
getData: getData
};
});
One way would be to cache the promise, rather than the data, so it gets cached when it is created and not when the data arrives. After all, this sounds like your use case.
angular.module('app', []).factory('dataService', function ($q) {
var deferred = null;
var getData= function (forceRefresh) {
console.log('getFolders called: ', reports.length);
if(!forceRefresh && deferred) return deferred.promise;
deferred = $q.defer();
WcfService.GetData(
function(result) { deferred.resolve(data); }, // I'd promisify at a
function(ex){ deferred.reject(ex); } // higher level probably
);
return deferred.promise;
};
return {
getData: getData
};
});
how about setting a global flag in the $rootscope when a controller is querying for data, which can be checked before fetching the data by all the controllers, hence, avoiding redundant calls. The same flag can be put down when any of the controller has the promise fulfilled and data has been fetched, which can then be shared amongst all the controllers.
I found exactly what I search for
Promise queue for AngularJS services
http://inspectorit.com/tips-tricks/promise-queue-for-angularjs-services/