Below are my 2 services. When routing to a url, I resolve "OtherService.promise". But for some routes, I do not want to use the promise, instead I want to use some scope variable in AppService method. How can I achieve this?
app.factory("AppService", function(OtherService){
var object = {};
object.method = function() {
return OtherService.get();
}
return object;
});
app.factory("OtherService", function($http) {
var data = null;
var promise = $http.get('/get').success(function (response) {
data = response
});
return {
promise: promise,
get: function(){
return data.html;
}
})
Edit: added a method in the service and a variable in its scope. In the controller you can do AppService.setScope($scope) and you're good to go.
Not sure I understand what you mean, but would something like this work:
app.factory("AppService", function(OtherService){
var scope = {};
var object = {};
object.method = function(mode) {
return mode === 'promise' ? OtherService.get() : scope;
}
object.setScope = function(_scope) {
scope = _scope;
}
return object;
});
app.factory("OtherService", function($http) {
var data = null;
var promise = $http.get('/get').success(function (response) {
data = response
});
return {
promise: promise,
get: function(){
return data.html;
}
})
Should probably add a check to see data is valid when you get, just to be safe.
Promises are there for you to use, to avoid nasty callback hell. What it seems you are trying to do is turn an asynchronous process into a synchronous process, which is somewhere that you do not want to go.
Embrace promises, they are EXTREMELY helpful readable, I suggest taking a look at the documentation.
Related
I have seen answers on StackOverflow where people suggest furnishing a callback function to an AngularJS service.
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens(function callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function(callbackFn) {
$http.get('/api/tokens').then (function onFulfilled(response) {
callbackFn(response.data);
});
};
return {
getTokens: getTokens
};
});
This seems to me to be an Anti-Pattern. The $http service returns promises and having .then methods execute callback functions feels like an unhealthy inversion of control.
How does one re-factor code like this and how does one explain why the original way was not a good idea?
You should change it to
var getTokens = function() {
return $http.get('/api/tokens');
};
And, then in other module use
yourModule.getTokens()
.then(function(response) {
// handle it
});
As to why it's an anti-pattern, I'd say that, first, it doesn't allow you to further chain your success/fail handler methods. Second, it handles the control of processing the response from caller-module to called module (which might not be super-important here, but it still imposes same inversion of control). And finally, you add the concept of promises to your codebase, which might not be so easy to understand for some of the teammates, but then use promises as callbacks, so this really makes no sense.
The code can be re-factored as follows:
app.controller('tokenCtrl', function($scope, tokenService) {
tokenService.getTokens.then ( callbackFn(tokens) {
$scope.tokens = tokens;
});
});
app.factory('tokenService', function($http) {
var getTokens = function() {
//return promise
return $http.get('/api/tokens').then (function onFulfilled(response) {
//return tokens
return response.data;
}
);
};
return {
getTokens: getTokens
};
});
By having the service return a promise, and using the .then method of the promise, the same functionality is achieved with the following benefits:
The promise can be saved and used for chaining.
The promise can be saved and used to avoid repeating the same $http call.
Error information is retained and can be retrieved with the .catch method.
The promise can be forwarded to other clients.
I don't familiar with JavaScript Promises function.
Currently I have this code on my Angular Controller
$http.get('pages/about.html').then(function(response) {
var raw_html = response.data;
$scope.aboutHTML = raw_html.replace(/</g,"<");
});
I want to re-write the code so I could do something like this
$scope.indexHTML = getHTML('pages/index.html');
$scope.aboutHTML = getHTML('pages/about.html');
...
with function like this
function getHTML(url){
$http.get(url).then(function(response) {
var raw_html = response.data;
return = raw_html.replace(/</g,"<");
});
}
How to write the the code properly for the function above?
[Update #1]
Temporary Solution by #charlietfl
function getHTML(url){
// return the promise
return $http.get(url).then(function(response) {
var raw_html = response.data.replace(/</g,"<");
return raw_html;
});
}
getHTML('pages/index.html').then(function(raw_html){
$scope.indexHTML = raw_html;
});
I wanna to write this function to reduce the manual work, with this way I still need to write down $scope.{page} for each page, so anyone know better way?
[Update #2]
Solution by #joeytwiddle
function getHTML(url){
// return the promise
return $http.get(url).then(function(response) {
var raw_html = response.data.replace(/</g,"<");
return raw_html;
});
}
getHTML('pages/index.html').then(function(raw_html){
$scope.indexHTML = raw_html;
});
There is no way to just return the result, because the result will not be available until some time in the future. #asynchronous
You can only handle the result using a callback function.
If you want to minimize the work from outside, I would suggest something like this:
getHTMLAndStore('pages/index.html', $scope, 'indexHTML');
getHTMLAndStore('pages/about.html', $scope, 'aboutHTML');
function getHTMLAndStore(url, object, property) {
$http.get(url).then(function(response) {
var raw_html = response.data;
var weird_html = raw_html.replace(/</g,"<");
object[property] = weird_html;
}).catch(console.error.apply(console));
}
This is pure JS and not really related to Angular.
Note that these two requests will fire in parallel, not in sequence.
$http returns a promise so you need to return that promise from the function and use another then() to assign the scope variable:
function getHTML(url){
// return the promise
return $http.get(url).then(function(response) {
var raw_html = response.data.replace(/</g,"<");
return raw_html;
});
}
getHTML('pages/index.html').then(function(raw_html){
$scope.indexHTML = raw_html;
});
Currently your function doesn't return anything
I'm using D.js as promise library for our javascript application.
Following is my sample code:
function getData(deferred) {
var data_one;
// getInfo is returning a promise for async task
getInfo()
.then(function (resp_one) {
data_one = resp_one;
// getInfo2 is also returning another promise
return getInfo2();
})
.then(function (resp_two) {
deferred.resolve('prefix' + data_one + resp_two);
});
};
function sample () {
var d = D(),
data = localStorage.getItem('key');
if (data) {
d.resolve(data);
} else {
getData(d);
}
return d.promise;
}
sample().then(function (data) {
//do something with data.
});
I im invoking sample function. Is the implementation inside the sample function and sub functions following the coding standard for promises?
Im new to promises, Is it good to passing around deferred object to other function to resolve/reject ??
Is there any better way to implement the above functionality??
Thanks in advance..
Looks like you can improve the code if you use promises in more natural way.
First of all if getData returns a Promise then you don't have to pass deferred around, this is considered anti-pattern. You just simply return getInfo().
Another thing, in the sample function if your data might be already available it's convenient to use D.promisify method to return resolved promise with non-promise value:
function getData() {
var data_one;
return getInfo().then(function (resp_one) {
data_one = resp_one;
return getInfo2();
})
.then(function (resp_two) {
return 'prefix' + data_one + resp_two;
});
};
function sample() {
var data = localStorage.getItem('key');
return data ? D.promisify(data) : getData();
}
sample().then(function (data) {
//do something with data.
});
I have a function that uses two ajax calls in order to get the proper information:
var getUsers = function() {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(foo) {
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js", function(bar) {
return foo['age'] = bar.type;
});
});
}
And an outside function that calls the current function and only continues when the calls are finished.
getUsers().then(function(result) {
// ...
});
Now the weird thing is that if I display the result, the 'age' will show up in the console, but if I try to access it using result['age'], it will return undefined.
Is there a proper way of handling multiple deferred calls?
Code
http://codepen.io/norbiu/pen/bNRQxL
Edit Instead of using a separate deferred, you can chain the ones returned from getJSON() like this
var getUsers = function() {
var foo;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.then(function(data) {
foo = data;
return $.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
}).then(function(bar) {
foo['age'] = bar.type;
return foo;
});
}
Note: you need to save the return value from the first call or it won't be accessible to the second.
Original code for posterity
You can use a jQuery Deferred object and return that instead
var getUsers = function() {
var dfd = $.Deferred();
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(foo) {
$.getJSON("http://codepen.io/chriscoyier/pen/EAIJj.js")
.done(function(bar) {
foo['age'] = bar.type;
dfd.resolve(foo);
}).fail(function(e) {
dfd.reject(e);
})
}).fail(function(e) {
dfd.reject(e);
});
return dfd.promise();
}
http://codepen.io/anon/pen/pvwqZo
The deferred object won't resolve until both requests succeed (and will fail if any of them fail).
I am working on making my existing code a bit better and I would like to have two objects for interacting with a RESTful API.
Previously, I used $http to get get the data and return the promise. I would then .then some actions to it. Then I would requery to get additional data and repeat it. So there were several nested $http queries. I would like to avoid that if possible.
I would like to have a service (or a factory) that I can use to configure various query parameters and another object for massaging and outputting the response.
Here is what I have so far:
var solr1 = angular.module('solr', ['ngResource']);
solr1.run(function(){
console.log("Module Loaded");
});
solr1.service('solrser', function($resource,solrresult) {
console.log("In Solrser")
this.serverurl = 'url';
this.res = $resource(this.serverurl);
this.query = function (p) {
this.res.get(p).$promise.then(function(d) {
return solrresult(d);
});
}
});
solr1.factory('solrresult',function() {
return function(a) {
this.data = a;
this.ready = 0;
console.log("In Factory")
this.getdocs = function() {
console.log("Getting Data")
console.log(this.data);
return this.data.docs; //this is line 9
}
return this;
}});
Controller looks like this:
app.controller('app1cont', ['$scope', 'solrser', 'solrresult', function($scope,solrser,solrresult){
console.log("Start");
var res = solrser.query({'q':'potato'});
console.log(res)
console.log(res.getdocs())
}]);
The output looks like this:
Module Loaded solr_module.js:5
In Solrser solr_module.js:9
Start main_controller.js:6
undefined main_controller.js:9
TypeError: Cannot read property 'getdocs' of undefined
at new <anonymous> (.........../main_controller.js:10:21)
at Object.e [as invoke] (https://code.angularjs.org/1.3.1/angular.min.js:36:365)
at F.instance (https://code.angularjs.org/1.3.1/angular.min.js:75:91)
at https://code.angularjs.org/1.3.1/angular.min.js:58:287
at s (https://code.angularjs.org/1.3.1/angular.min.js:7:408)
at G (https://code.angularjs.org/1.3.1/angular.min.js:58:270)
at g (https://code.angularjs.org/1.3.1/angular.min.js:51:172)
at g (https://code.angularjs.org/1.3.1/angular.min.js:51:189)
at https://code.angularjs.org/1.3.1/angular.min.js:50:280
at https://code.angularjs.org/1.3.1/angular.min.js:18:8 angular.js:11339
In Factory solr_module.js:25
So the factory gets created after the controller resumes execution. What am I doing wrong here?
You have a timing issue.
app.controller('app1cont', ['$scope', 'solrser', 'solrresult', function($scope,solrser,solrresult){
console.log("Start");
var res = solrser.query({'q':'potato'});
console.log(res)
console.log(res.getdocs())
}]);
When you call solrser.query() it runs async to get the data. So the res is not populated with any real value until after the promise returns inside the "solrser" service. You would need to do something more like:
solrser.query({'q':'potato'},function(res) {
console.log(res);
console.log(res.getdocs());
}); // could be done as a promise as well....
Separately, you also are doing something really strange when the response is called inside the solrser service.
this.query = function (p) {
this.res.get(p).$promise.then(function(d) {
return solrresult(d);
});
}
Calling return from within the promise handler is not going to do you very much. You need to either call a callback or resolve a deferred. Something like this would work better:
this.query = function (p,callback) {
this.res.get(p).$promise.then(function(d) {
callback(solrresult(d));
});
}
Or as a promise
this.query = function (p) {
var defer = $q.defer();
this.res.get(p).$promise.then(function(d) {
defer.resolve(solrresult(d));
});
return defer.promise;
}
Or you could just pass the get(p).$promise through, etc.
UPDATE:
Since this is an async action, there will always be a promise or callback. You have to do one of:
app.controller('app1cont', ['$scope', 'solrser', 'solrresult', function($scope,solrser,solrresult){
var res = solrser.query({'q':'potato'},function() {
res.getDocs();
});
}]);
Or just straight callback
app.controller('app1cont', ['$scope', 'solrser', 'solrresult', function($scope,solrser,solrresult){
solrser.query({'q':'potato'},function(res) {
res.getDocs();
});
}]);
I see missing semicolon
console.log("In Solrser")
shouldbe
console.log("In Solrser");
also missing semicolon here
console.log("Getting Data")
console.log("In Factory")
in controller you have missing semicolons too.
I think this is a reason why your code don't work.
To avoid bug like missing semicolon you can use JSHint plugin in your IDE