I have the following code
$scope.getBooksByUser = function (user) {
var books = [];
ProductFactory.getBooks.query({ id: user.id }).$promise.then(function (result) {
angular.forEach(result, function(i) {
books.push(i.name);
debugger;
})
});
};
As you can see, I am trying to fill a simple javascript array in angular for each. When this function is executed, an array books is still empty.
I am little confused with this behavior, cause when i try to debug my code i can see that on each iteration value is set in array. Actually chrome debugger shows me that an array books is in closure scope and i guess that is a reason but i still cant find an explanation and how i can fix it.
Actually if i was using $scope.books = [] it would be work fine, but i dont need $scope, but i need just a javascript array.
Thanks in advance.
I guess it is happening because of asynchronous call of AJAX. In your case compiler will not wait for your ajax response. You should do it like
ProductFactory.getBooks.query({ id: user.id }).$promise.success(function (result) {
angular.forEach(result, function(i) {
books.push(i.name);
debugger;
})
});
Hope it helps :-)
Related
I've stumbled upon (or created myself of course) an error that I cannot model in my head. I'm iteratively calling an URL using the webdriverio client with different IDs and parsing the resulting HTML. However, the html variable gets overwritten with the last element in the loop, which results in the array containing multiple duplicates of the last html variable value:
async.forEach(test, function (id, callback) {
self.url('https://<api-page>?id=' + id).getHTML('table tbody', true).then(function(html) {
//Parse HTML
parser.write(html);
parser.end();
//Add course to person, proceed to next.
callback();
});
}, function (err) {
self.end().finally();
res.json(person);
});
Parsing is done using the htmlparser2 NPM library. The html variable always returns the last element, even though I can see it going through the different API ids with different data. I would think the error lies at when I get HTML and return it, but I cannot say why nor have any of my fixes worked.
Hopefully someone more skilled than me can see the error.
Thanks in advance,
Chris
UPDATE/Solution - See solution below
I am not sure if I understood quite well the context but the html variable is not overridden, it is just the last chunk that you 've retrieved from the self.url function call. If you want to have the whole result saved in a variable, you should keep append on every loop the result. Probably, you need something like that:
var html = '';
async.forEach(test, function (id, callback) {
self.url('https://<api-page>?id=' + id).getHTML('table tbody', true).then(function (tmpHtml) {
//Parse HTML
parser.write(tmpHtml);
parser.end();
html += tmpHtml;
//Add course to person, proceed to next.
callback();
});
}, function (err) {
self.end().finally();
res.json(person);
});
I finally figured it out and I missed that async.forEach executes the function in parallel, whereas the function I needed was async.timesSeries, which executes the functions in a loop, waiting for each function to finish before starting the next! I've attached the working code below:
async.timesSeries(3, function(n, next) {
self.url('<api-page>?id=' + n').then(function() {
console.log("URL Opened");
}).getHTML('table tbody', true).then(function(html) {
console.log("getHTML");
parser.write(html);
parser.end();
next();
});
}, function(err, results) {
//Add to person object!
self.end().finally();
res.json(person);
});
I'm writing Telerik platform hybrid app. I want to write function which returns a number from Everlive cloud. Here's my code.
function getSector(){
var result = 0;
var data = el.data('csector');
data.get()
.then(function (data) {
var obj = JSON.parse(JSON.stringify(data));
//alert(obj.result[0].current);
result = obj.result[0].current;
},
function (error) {
alert(JSON.stringify(error));
});
return result;}
Problem is that I cant't return value. It always return 0. Seems like result can't be changed from nested function? Alert works great. Please help.
Documentation
If you are calling your function from somewhere in JS and other than in your markup directly (data binding), then you may need to implement promises through jQuery, angular or some other library. Here is jQuery's implementation: https://api.jquery.com/promise/
If you are calling the function through data binding then your function should modify a property on your model and then your markup should be bound to that property. Without more information about your project it is hard to answer definitively.
Sample
Please consider this Plunk.
Background
The Plunk is a seriously simplified sample of what I need to do. Basically I need to get a person record with validation result included. This result will also include class information, so that the controller can assign proper styling to indicate mandatory status (or not).
The issue
The most important function in the sample is this one, the self.Get will use the validation logic and result a result.
self.Get('0f8fad5b-d9cb-469f-a165-70867728950e').then(function(result){
$scope.person = result.person;
$scope.validationResult = result.ValidateResult;
});
As you can see (because the form has correct values) $scope.person is loaded correctly, the $scope.validationResult however is not.
The question
I suspect there's a timing issue with the consecutive async calls, how can I fix this Plunk so that everything works correctly?
Nesting async calls in each other would be 'one' solution, I guess, but given the number of calls being made, that would not solve everything and the code would become highly unreadable.
For multiple async calls I would suggest to use array of promises:
var promises = [];
promises.push(service1.get(...))
promises.push(service2.get(...))
promises.push(service3.get(...))
return $q.all(promises);
So, how do I convert the following code to use chaining, while keeping all dependencies intact?:
self.Get = function (personId) {
// 1. Init
var defer = $q.defer();
var asyncCallsResult = {};
// 2. Get person
var person = self.GetTestPerson();
asyncCallsResult.person = person;
self.LoadPersonDetailProxy(person).then(function(personDetailProxy) {
$scope.personDetailProxy = personDetailProxy;
});
validationService.ValidateAsync($scope.personDetailProxy).then(function (validateResult) {
asyncCallsResult.ValidateResult = validateResult;
});
defer.resolve(asyncCallsResult);
return defer.promise;
}
I wont handled promise in other function, but after worked first callback variable not change. Please help me. See my code here:
this.handlerLocalDef = function(defer) {
var hash = {};
defer.then(
function(response) {
hash = response;
},
function(err) {
showPopup(err);
}
);
return hash;
};
var initialized = function() {
var localRegDef = Localization.getLocalizedDefer('regularform'),
localPaymDef = Localization.getLocalizedDefer('payment');
localizeRegForm = self.handlerLocalDef(localRegDef, localizeRegForm);
$timeout(function() {
console.log("localizeRegForm", localizeRegForm);
},5000);
}();
console log return me localizeRegForm: {}
By doing hash = response you are only setting the local variable to reference another object. The receiver of the original returned object still keeps that object. A quick solution is to do angular.extend(hash,response);. This will copy all members of respose into the object referenced by hash, which is the original returned object. So the receiver will suddenly see those members.
Caveat: will neither work for values (strings, numbers, booleans, undefined, null) nor for arrays.
This is a fiddle with the above suggestion that works: http://jsfiddle.net/sVRCg/3/
However I get the feelig that the entire setup is wrong. How will you know that the hash is actually extended with response? Polling (e.g. setTimeout) is an awkward solution. I suggest you returned the promise instead; this way you can find out exactly when the response is available.
Another way is how ngResource does it; this is quite complicated, check out the code.
That's not how you do async programming.
You do not set an arbitrary timeout and hope that you have data at that point, you must use the deferred object the way it was intended and use callbacks. This is one way of restructuring your code so that it will work. Depending on your specific circumstances, it may or may not serve your purpose:
this.handlerLocalDef = function(defer,callback) {
defer.then(
function(response) {
callback.apply(this,[response])
},
function(err) {
showPopup(err);
}
);
};
var initialized = function() {
var localRegDef = Localization.getLocalizedDefer('regularform'),
localPaymDef = Localization.getLocalizedDefer('payment');
self.handlerLocalDef(localRegDef,function(response) {
console.log(response);
});
}();
I feel stupid because I've been trying to access this response variable for a while and I guess I do not understand closures or scope well enough, so please help.
I'm working on a chrome extension and I am sending a message from contentscript.js to background.js and receiving the response. Now I want to return the response and be able to use it in contentscript.js. Seems like something you should be able to do...
function getWords(){
var words = [];
chrome.runtime.sendMessage({detail: "words"}, function(response) {
console.log(response) // prints ["word1, "word2" ..]
words = response;
});
return words; // = []
}
UPDATE:
Thanks, I understand what my issue is now, but still would like some advice to solve it.
My question is what is the best way to "ask" the background page for a list of words if I need it immediately as a parameter in another function. Can I wait for the information to come back? Should I simply call that other function from the callback? or is there some other method?
Ideally I would like to actually implement a getWords() that doesn't return until the list comes back... Impossible? I'm open to open source libraries as well.
Because sendMessage is an asynchronous call and you are treating it as a synchronous one. You are trying to read words before the call is actually made. There is no way to wait for it. You need to use callbacks.
function getWords( callback ){
var words = [];
chrome.runtime.sendMessage({detail: "words"}, function(response) {
console.log(response) // prints ["word1, "word2" ..]
callback(response);
});
}
function processWords(words){
//do your logic in here
console.log(words);
}
getWords(processWords);