Javascript Promises on Angular Controller - javascript

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

Related

Angular $promise without using defer

Everything on the net about promises these days says don't use defer when using promises except docs/examples on angular.js.
What is the proper way to return the value of an angular.js $promise without using defer?
Here's what I currently have:
function foo() {
var deferred = $q.defer();
userService.findProgramsByUser({personId: personId}).$promise
.then(function (programs) {
deferred.resolve(programs);
});
return deferred.promise;
}
If i was using the node Q library I would do this:
function foo() {
return Q.promise(function(resolve, reject) {
userService.findProgramsByUser({personId: personId})
.then(function (programs) {
resolve(programs);
});
}
}
How can I do something similar with an angular promise without having to use defer?
Since userService.findProgramsByUser() already has a promise property, just return that
function foo() {
return userService.findProgramsByUser({personId: personId}).$promise
.then(function (programs) {
return programs;
}, function(){
// handle errors
});
}
I really don't understand why it's bad to use defer. In most cases my services looks like:
var defers = {}; //somewhere in the service root.
getData: function(id){
var defKey = 'getData' + id;
if(defers.hasOwnProperty(defKey)) return defers[defKey].promise;
//here is a good spot to return something from custom cache
defers[defKey] = $q.defer();
serverApiRequest('/api/...').then(function(response){
defers[defKey].resolve(response.data);
delete defers[defKey];
});
return defers[defKey].promise;
}
Deferred promises are awesome. With such approach we can be 100% sure that server will be requested only one time and every parallel requests would be served.

What is the best way to implement promise in the given scenario?

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.
});

Returning new Object from Service that uses $resource

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

How can I pass $scope to a service in angular.js?

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.

Can I use $q.all in AngularJS with a function that does not return a .promise?

If I have the following functions:
doTask1: function ($scope) {
var defer = $q.defer();
$http.get('/abc')
.success(function (data) {
defer.resolve();
})
.error(function () {
defer.reject();
});
return defer.promise;
},
doTask2: function ($scope) {
var defer = $q.defer();
var x = 99;
return defer.promise;
},
I'm told that I can wait for both promises like this:
$q.all([
doTask1($scope),
doTask2($scope)
])
.then(function (results) {
});
How about if task 2 does not return a promise? I saw in the $q documentation
for AngularJS that there is a "when". However I am not clear on how to use it
and there's no example.
Is it the case that I MUST have doTask2 return a promise by having the two lines:
var defer = q.defer()
return defer.promise
or is there an easier way to do this?ll
$q.when is used in scenarios where you don't know upfront whether the function is returning a promise or a direct value.
The following example/plunker shows a method, whose result is used in $q.all, and which returns different type of object (int or promise) every time it's called:
PLUNKER
app.controller('MainController', function($scope, $q, $http) {
var count = 0;
function doTask1() {
var defer = $q.defer();
$http.get('abc.json')
.success(function (data) {
defer.resolve(data);
})
.error(function () {
defer.reject();
});
return defer.promise;
}
/**
* This method will return different type of object
* every time it's called. Just an example of an unknown method result.
**/
function doTask2() {
count++;
var x = 99;
if(count % 2){
console.log('Returning', x);
return x;
} else {
var defer = $q.defer();
defer.resolve(x);
console.log('Returning', defer.promise);
return defer.promise;
}
}
$scope.fetchData = function(){
// At this point we don't know if doTask2 is returning 99 or promise.
// Hence we wrap it in $q.when because $q.all expects
// all array members to be promises
$q.all([
$q.when(doTask1()),
$q.when(doTask2())
])
.then(function(results){
$scope.results = results;
});
};
});
<body ng-app="myApp" ng-controller='MainController'>
<button ng-click="fetchData()">Run</button>
<pre>{{results|json}}</pre>
</body>
is there an easier way to do this [than manually constructing and resolving a deferred and returning a promise]?
Yes, use the $q.when function:
doTask2: function ($scope) {
return $q.when( 99 );
},
However, you don't actually need to do this. $q.all will - though not stated in the docs - also work with non-promise values (implementation calls _ref which converts it). So just
return 99;
is fine as well. However, if you know beforehand that it's synchronous, using promises doesn't seem to make much sense.

Categories

Resources