I want to access the updated value out site the scope like the following scenario.
Var data = [];
var getData = function()
{
var onSuccess = function(result)
{
data = result;
}
var onError = function(err)
{
console.log(err);
}
ApiCall().then(onSuccess, onError);
}
getData();
console.log(data);
Here in the above case data is always coming blank array [] not the updated one. how can i access the updated data outside the scope?
You can't and you shouldn't. You need to synchronize your code if you want it to run asynchronously. You can do this by using the promise chain:
return ApiCall().then(onSuccess, onError);
...
getData().then(console.log);
Try the following - you basically need to return your promise.
Var data = [];
var getData = function()
{
var onSuccess = function(result)
{
data = result;
return data;
}
var onError = function(err)
{
console.log(err);
}
return ApiCall().then(onSuccess, onError);
}
getData().then(function (data) { console.log(data); });
Related
I have a javascript function which for this question I have simplified. It actually does some things to the data retrieved from the $http call and then I want that data to be made available along with a promise to the function that called it:
getTopics = (queryString: string) => {
var self = this;
var defer = self.$q.defer();
self.$http({
// cache: true,
url: self.ac.dataServer + '/api/Topic/GetMapData' + queryString,
method: "GET"
})
.success((data) => {
var output: ITopics = {
details: data
}
// output is correctly populated with data
defer.resolve(output);
// I also tried this and it get seen in the calling function either
// defer.resolve('abc');
})
return defer.promise;
};
This calls it:
return topicService.getTopics("/" + subjectService.subject.id)
.then((data) => {
// data seems to be not defined
var x = data;
});
Can someone tell me what I might be doing wrong. I thought the resolve would return data also but it seems not to be doing so.
Try this syntax, which is what I swtiched to when I moved some existing $q code to ES6
getTopics(queryString) {
return this.$q((resolve, reject) => {
this.$http.get(self.ac.dataServer + '/api/Topic/GetMapData' + queryString)
.success((data) => {
resolve({details: data});
})
.error( (err) => reject(err) );
});
}
or much better, as suggested by Bergi:
getTopics(queryString) {
return this.$http.get(self.ac.dataServer + '/api/Topic/GetMapData' + queryString)
.then( data => ({details: data}) );
});
}
$http.get().then(...) is itself a promise that can be further used by the calling code.
Perhaps it's a matter of personal preference, but I use the $q.defer() technique to "promisify" a synchronous function. In case of something that's already asynchronous, i.e, already a promise, like $http and $timeout, I set a success handler using the then function, modify the result as necessary and return the object as a promise using $q.when()
Refer to the when function in the $q documentation
I've created a Plnkr (in Javascript) that demonstrates the use of $q.when in this situation. Relevant code:
$scope.results = ['a', 'b', 'c', 'd'];
$scope.getTopics = function() {
//Replace $timeout with $http
// $http({
// }).then....
return $timeout(function() {
console.log('Simulating HTTP response');
return $scope.results;
}, 1500)
.then(function(res) {
var updatedRes = [];
//Modify the HTTP/timeout results
console.log('Modifying HTTP/timeout result');
angular.forEach(res, function(item) {
item = item + '1';
updatedRes.push(item);
});
//Wrap the result object in $q.when to create a promise
return $q.when(updatedRes);
});
};
$scope.getTopics().then(function(data) {
console.log('Using the modified result');
$scope.updatedResults = data;
console.log('getTopics result = ', data);
});
Try this one.
return topicService.getTopics("/" + subjectService.subject.id)
.then((data): void => {
// data seems to be not defined
var x = data;
} // ADDED
);
I think it's because of the missing } in the code that's why data doesn't have any value in the getTopics function.
Have a following node.js call to http get REST API. When I run it, it returns the JSON result. However, I am trying to get the result to console.log on var r after assigning the result. Why do I always get undefined? Looks like I am messing up the javascript scope. Any help is appreciated.
var http = require('http'),
oex_appId = 'myappId',
_hostname = 'openexchangerates.org',
_path = '/api/latest.json?app_id=' + oex_appId;
var r;
function getRates(cb) {
var options = {
hostname: _hostname,
path: _path,
method: 'GET'
};
var responseCallback = function(response) {
var latestRates = '';
var parseCurrencies = function(rates) {
var currencies = JSON.parse(rates);
currencies.rates['USD'] = 1;
return currencies.rates;
};
response.setEncoding('utf8');
response.on('data', function(chunk) {
latestRates += chunk;
}).on('error', function(err) {
throw new Error(err);
}).on('end', function() {
cb(parseCurrencies(latestRates));
});
};
var req = http.request(options, responseCallback);
req.on('error', function(e) {
throw e;
});
req.end();
};
var d = function(data) {
console.log(data["INR"]);
currencies["INR"].amount = data["INR"];
r = data;
};
getRates(d);
console.log(r);
Why do I always get undefined?
Your problem is not an scope issue but a misunderstanding of async execution. In this code you are printing the value of r before it is assigned.
The execution of your code is as follow:
getRates() is called
console.log(r) is called (hence you get undefined)
When the request executed in getRates finishes your callback is THEN executed. This is when you are assigning a value to r but by then is too late since you already printed it.
In general, you cannot expect the next line to have a value that will be assigned via an async callback. Instead you should reference the value inside the callback. In your case you should move the console.log(r) inside your callback function d.
Basically I have the function getUserInfo which has one function that returns available and assigned groups within the service. Then I have the other two functions below return those objects. However, I can't run the getAssignedGroups() and getAvailableGroups() before the first function is done. So I thought I'd use the then() to ensure those two ran once the first function was completed.
I have this function in a controller:
$scope.getUserInfo = function(selectedUser) {
$scope.userGroupInfo = groupService.getUserGroups(selectedUser.domain,$scope.groups).then(
$scope.assignedGroups = groupService.getAssignedGroups(),
$scope.availableGroups = groupService.getAvailableGroups()
);
};
This is my service:
spApp.factory('groupService', function () {
var assignedGroups, availableGroups, allGroups;
var getGroups = function () {
allGroups = [];
$().SPServices({
operation: "GetGroupCollectionFromSite",
completefunc: function(xData, Status) {
var response = $(xData.responseXML);
response.find("Group").each(function() {
allGroups.push({
id: $(this).attr('ID'),
name: $(this).attr('Name'),
Description: $(this).attr('Description'),
owner: $(this).attr('OwnerID'),
OwnerIsUser: $(this).attr('OwnerIsUser'),
});
});
}
});
return allGroups;
}
var getUserGroups = function (selectedUser, allGroups) {
assignedGroups = [];
$().SPServices({
operation: "GetGroupCollectionFromUser",
userLoginName: selectedUser,
completefunc: function(xData, Status) {
var response = $(xData.responseXML);
response.find("errorstring").each(function() {
alert("User not found");
booErr = "true";
return;
});
response.find("Group").each(function() {
assignedGroups.push({
id: $(this).attr('ID'),
name: $(this).attr('Name'),
Description: $(this).attr('Description'),
owner: $(this).attr('OwnerID'),
OwnerIsUser: $(this).attr('OwnerIsUser'),
});
});
}
});
//from here I start comparing All Groups with user groups to return available groups
var assignedGroupsIds = {};
var groupsIds = {};
var availableGroups = [];
assignedGroups.forEach(function (el, i) {
assignedGroupsIds[el.id] = assignedGroups[i];
});
allGroups.forEach(function (el, i) {
groupsIds[el.id] = allGroups[i];
});
for (var i in groupsIds) {
if (!assignedGroupsIds.hasOwnProperty(i)) {
availableGroups.push(groupsIds[i]);
}
}
/* return {
assignedGroups:assignedGroups,
availableGroups:availableGroups
}*/
}
var getAvailableGroups = function () {
return availableGroups;
}
var getAssignedGroups = function () {
return assignedGroups;
};
return {
getGroups:getGroups,
getUserGroups:getUserGroups,
getAvailableGroups: getAvailableGroups,
getAssignedGroups:getAssignedGroups
}
});
You can inject the Angular promise module into your factory:
spApp.factory('groupService', function ($q) {
Then inside the getUserGroups() function of your factory, declare a deferred object:
var deferred = $q.defer();
Then you need to use deferred.resolve(response) inside your async request and return deferred.promise from your function. Also, the then function takes a function with the returned response as the argument (so you can then access the response from the controller):
$scope.userGroupInfo = groupService.getUserGroups(selectedUser.domain,$scope.groups).then(function(resp) {
$scope.assignedGroups = groupService.getAssignedGroups(),
$scope.availableGroups = groupService.getAvailableGroups()
});
Look into the AngularJS docs on $q for more info.
Better approach will be use callback approach. You can take following approach.
$scope.getUserInfo = function(selectedUser, getUserInfoCallback) {
$scope.userGroupInfo = groupService.getUserGroups(selectedUser.domain,$scope.groups, function (response){
$scope.assignedGroups = groupService.getAssignedGroups( function (response){
$scope.availableGroups = groupService.getAvailableGroups( function(response){
//Here call parent callback
if(getUserInfoCallback)
{
getUserInfoCallback(response); //If you need response back then you can pass it.
}
})
})
})
);
};
Now you need to define each method as callback. Following is sample method for your reference.
$scope.getAssignedGroups = function (getAssignedGroupsCallback){
//Do all operation
//Now call callback
if(getAssignedGroupsCallback)
{
getAssignedGroups();//you can pass data here which you would like to get it return
}
};
Note: I have just added the code to clarify the approach. You need to change it to compile and implement your own logic.
Summary of approach: All services are invoked asynchronously therefore you need to pass callback method as chain (to keep your code clean) so that your code can wait for next step.
Hope it helps.
I am following this Angular tutorial on promises. I have a service that checks if an array is empty and if so, hits a REST service, returns a promise and updates the array. Here is the relative code:
requestingProviders: [],
getRequestingProviders: function() {
var that = this;
var deferred = $q.defer();
if(this.requestingProviders.length === 0) {
this.getResource().search({
role: 'REQUESTING_PROVIDER'
}, function(data) {
that.requestingProviders = data.providers;
deferred.resolve(data.providers);
});
return deferred.promise;
} else {
return that.requestingProviders;
}
}
The service is being called from a controller. Here is the code where it is being called:
$scope.providers = providerService.getRequestingProviders();
The REST call is made and returns fine, but the view is never updated. This is not working like the tutorial explained. Here is a plunker that shows what I am expecting. What am I doing wrong?
You need to resolve your promise:
var prom = providerService.getRequestingProviders();
prom.then(function (data) {
$scope.providers = data;
});
Also, change your code to always return the promise:
getRequestingProviders: function() {
var that = this;
var deferred = $q.defer();
if(this.requestingProviders.length === 0) {
this.getResource().search({
role: 'REQUESTING_PROVIDER'
}, function(data) {
that.requestingProviders = data.providers;
deferred.resolve(data.providers);
});
} else {
deferred.resolve(that.requestingProviders);
}
return deferred.promise;
}
I have factory that's using indexedDB and method getRecipe that needs this indexed db to receive data.
Problem is that indexedDB returns it's instance in asynchronous call and getRecipe is another method that returns it's value in asynchronous call.
I try to solve it via promises, but I failed.
app.factory('RecipesStorage', ['$q', function($q) {
var getDb = function () {
var deferred = $q.defer();
var db;
var request = indexedDB.open('recipes', 1);
request.onupgradeneeded = function (e) {
console.log('Upgrading indexedDb');
var thisDb = e.target.result;
if (!thisDb.objectStoreNames.contains('recipe')) {
thisDb.createObjectStore('recipe');
}
}
request.onsuccess = function (e) {
db = e.target.result;
window.db = db;
deferred.resolve(db);
}
request.onerror = function (e) {
console.error('Error when opening indexedDB');
}
return deferred.promise;
};
var getRecipe = function (recipeId, callback) {
getDb().then(function(db) {
var transaction = db.transaction(['recipe'], 'readonly');
var objectStore = transaction.objectStore('recipe');
var data = objectStore.get(recipeId);
data.onsuccess = function (e) {
var result = e.target.result;
console.log('GetRecipe:', result);
callback(result);
}
data.onerror = function (e) {
console.error('Error retrieving data from indexedDB', e);
}
});
};
return {getRecipe:getRecipe};
}]);
But this doesent work. In getRecipe the function in then isn't invoked. I don't know where is problem.
We can use promise chain to make it work.
(I don't have database so i simulated async response and wrapped data with $q)
Demo Fiddle
fessmodule.controller('fessCntrl', function ($scope, RecipesStorage) {
$scope.alertSwap = function () {
RecipesStorage.getDb()
.then(function (result) {
$scope.data = result;
}, function (result) {
alert("Error: No data returned");
});
}
});
fessmodule.$inject = ['$scope', 'RecipesStorage'];
fessmodule.factory('RecipesStorage', ['$q', function ($q) {
var getDb = function () {
var data = [{
"PreAlertInventory": "5.000000",
"SharesInInventory": "3.000000"
} ];
var deferred = $q.defer();
deferred.resolve(data);
return getRecipe(data);
};
var getRecipe = function(db){
var data = [{
"TotalSharesBought": "0.000000",
"TotalShareCost": "0.000000",
"EstimatedLosses": "0.000000"
}];
db.push(data[0]);
var deferred = $q.defer();
deferred.resolve(db);
return deferred.promise;
}
var factory = {
getDb: getDb
};
return factory;
}]);
Reference
You can chain promises to create code flows
Error propagates, so you can catch it on the end of the chain
I can't see a problem in your code. Maybe this plunker helps you to figure out where the problem is. I created it based on your code and it apparently works.
The real problem was that in my version I've used angular in version 1.0.8, but when I switched it to version 1.2.0 it starts working as expected. Thanks :)