How to implement EcmaScript2015 promises in Angular? - angularjs

Is there a nice solution to get rid of Angular $q promise service and start using EcmaScript6 native promises?

I would say rather than using $q custom promise, you could utilize the promise return by the $http/$resource call itself. Because that has been taken care $http/$resource internal implementation, as they will run digest once their promise rejected/resolved.
The only benefit I can see is, by having ES2015 promise is, you could use Arrow function's (=>), which is shorter and smarter syntax.
Code
var promise = $http.get('someUrl');
promise.then((data) = > { //success callback
console.log(data)
}, (error) => { //error callback
console.log(error)
})

Related

What is the usage of $q and promise when we need callback for several methods

I am using angularJS and trying to creating amazon VPC through aws.sdk.js
Now when I create VPC I have to done various configuration and call below methods in chain
create vpc () => vpc_
create internet gateway () => igw_
attach internet gateway (vpc_, igw_) =>
create subnet (vpc_) => subnet_
create route table (vpc_) => rtb_
associate route table (rtb_, subnet_) =>
create security group (vpc_) => sg_
add inbound rule (sg_) =>
additionally create tags for each generated resources .
as you can see some function parameter depends on previous function and so on. I am using $q services on each AWS method but this is became so large callback hell.
I also capture notifications of each function in back so my input function became like this
functionOne().then(function(res1) {
console.info(res1);
functionTwo(res1.id).then(res2) {
console.info(res2);
......... series of functions within function....
----------------
----------------
.then(res7) { functionEight(res7.id) {
}, function (err) {}, function (notify) {})
.finally(function(){ console.log('Finally done everything'); });
---------------------
---------------------
}, function (err2) {
console.error(err2);
}, function (notify2) {
console.log(nofify2);
});
}, function (err1) {
console.error(err1);
}, function (notify1) {
console.log(nofify1);
});
my function signature is as below
functionX: function() {
var d = $q.defer();
var params = {
//...
};
ec2.anyMethodName(params, function(err, data) {
if (err) {
d.reject(err);
} else {
d.notify('Notifications methods');
d.resolve(data);
}
});
return d.promise;
}
You can see the chaining of methods in my controller here
So my question is
Does Amazon native thenable promise property can be used in angular? if yes then how do we implement notifications?
Is there any sorter way to do that? like error will be captures in last? using $.all() but do not not how?
If i use .finally() on functionOne than it throw error in console
.finally is not a function
Why don't you
functionOne()
.catch(errorHandlerOne)
.then(functionTwo) // calls functionTwo(responseOne)
.catch(errorHandlerTwo)
.then(functionThree) // calls functionThree(responseTwo)
.catch(errorHandlerThree)
.finally(finalHandler)
Edit: If you want to access the result of functionOne in functionThree, you can do the following:
functionTwo(resultOne) {
var promise = d.promise;
promise.then(function(resultTwo) {
return [resultOne, resultTwo];
});
return d.promise
}
Refer to this answer for more details.
You don't need to necessarily have to write anonymous callbacks as promise resolve and reject handlers
Your callbacks will be called with the resolved/rejected promise from the previous function automatically.
If you want your entire chain to fail if any one promise fails, you call always use .all(), so that's something you should pay attention to if you want to use .all().
To chain promises, return values to the success handlers; throw values to the rejection handlers:
functionOne().then(function onSuccess(res1) {
console.info(res1);
//return promise to chain
return functionTwo(res1.id);
}).catch(function onReject(error1) {
console.log(error1)
//throw to chain rejection
throw error1;
}).then(function onSuccess2(res2) {
console.info(res2);
//return promise to chain
return functionThree(res2.id);
}).then(function onSuccess3(res3) {
console.info(res3);
//return promise to chain
return functionFour(res3.id);
}).then(function onSuccess4(res4) {
console.info(res4);
//return to chain
return res4.id;
});
It is very important to throw to rejection handlers. If a rejection handler simply does a console log and omits the throw statement, the function returns undefined and the rejection gets converted. The next success handled will be invoked with undefined as its argument.
From the Docs:
Chaining promises
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
--AngularJS $q Service API Reference -- Chaining Promises
For more information, see SO: Angular execution order with $q -- Chaining Promises
Does Amazon native thenable promise property can be used in angular?
Promises from an external source such as the AWS API can be converted to a $q service with $q.when.
var angularPromise = $q.when(AWS.request.promise());
From the DOCS:
when
Wraps an object that might be a value or a (3rd party) then-able promise into a $q promise. This is useful when you are dealing with an object that might or might not be a promise, or if the promise comes from a source that can't be trusted.
--AngularJS $q Service API Reference -- $q.when

Typescript async/await doesnt update AngularJS view

I'm using Typescript 2.1(developer version) to transpile async/await to ES5.
I've noticed that after I change any property which is bound to view in my async function the view isn't updated with current value, so each time I have to call $scope.$apply() at the end of function.
Example async code:
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
//$scope.$apply(); <-- would like to omit this
}
And new text value isn't shown in view after this.
Is there any workaround so I don't have to manually call $scope.$apply() every time?
The answers here are correct in that AngularJS does not know about the method so you need to 'tell' Angular about any values that have been updated.
Personally I'd use $q for asynchronous behaviour instead of using await as its "The Angular way".
You can wrap non Angular methods with $q quite easily i.e. [Note this is how I wrap all Google Maps functions as they all follow this pattern of passing in a callback to be notified of completion]
function doAThing()
{
var defer = $q.defer();
// Note that this method takes a `parameter` and a callback function
someMethod(parameter, (someValue) => {
$q.resolve(someValue)
});
return defer.promise;
}
You can then use it like so
this.doAThing().then(someValue => {
this.memberValue = someValue;
});
However if you do wish to continue with await there is a better way than using $apply, in this case, and that it to use $digest. Like so
async testAsync() {
await this.$timeout(2000);
this.text = "Changed";
$scope.$digest(); <-- This is now much faster :)
}
$scope.$digest is better in this case because $scope.$apply will perform dirty checking (Angulars method for change detection) for all bound values on all scopes, this can be expensive performance wise - especially if you have many bindings. $scope.$digest will, however, only perform checking on bound values within the current $scope making it much more performant.
This can be conveniently done with angular-async-await extension:
class SomeController {
constructor($async) {
this.testAsync = $async(this.testAsync.bind(this));
}
async testAsync() { ... }
}
As it can be seen, all it does is wrapping promise-returning function with a wrapper that calls $rootScope.$apply() afterwards.
There is no reliable way to trigger digest automatically on async function, doing this would result in hacking both the framework and Promise implementation. There is no way to do this for native async function (TypeScript es2017 target), because it relies on internal promise implementation and not Promise global. More importantly, this way would be unacceptable because this is not a behaviour that is expected by default. A developer should have full control over it and assign this behaviour explicitly.
Given that testAsync is being called multiple times, and the only place where it is called is testsAsync, automatic digest in testAsync end would result in digest spam. While a proper way would be to trigger a digest once, after testsAsync.
In this case $async would be applied only to testsAsync and not to testAsync itself:
class SomeController {
constructor($async) {
this.testsAsync = $async(this.testsAsync.bind(this));
}
private async testAsync() { ... }
async testsAsync() {
await Promise.all([this.testAsync(1), this.testAsync(2), ...]);
...
}
}
I have examined the code of angular-async-await and It seems like they are using $rootScope.$apply() to digest the expression after the async promise is resolved.
This is not a good method. You can use AngularJS original $q and with a little trick, you can achieve the best performance.
First, create a function ( e.g., factory, method)
// inject $q ...
const resolver=(asyncFunc)=>{
const deferred = $q.defer();
asyncFunc()
.then(deferred.resolve)
.catch(deferred.reject);
return deferred.promise;
}
Now, you can use it in your for instance services.
getUserInfo=()=>{
return resolver(async()=>{
const userInfo=await fetch(...);
const userAddress= await fetch (...);
return {userInfo,userAddress};
});
};
This is as efficient as using AngularJS $q and with minimal code.
As #basarat said the native ES6 Promise doesn't know about the digest cycle.
What you could do is let Typescript use $q service promise instead of the native ES6 promise.
That way you won't need to invoke $scope.$apply()
angular.module('myApp')
.run(['$window', '$q', ($window, $q) => {
$window.Promise = $q;
}]);
I've set up a fiddle showcasing the desired behavior. It can be seen here: Promises with AngularJS.
Please note that it's using a bunch of Promises which resolve after 1000ms, an async function, and a Promise.race and it still only requires 4 digest cycles (open the console).
I'll reiterate what the desired behavior was:
to allow the usage of async functions just like in native JavaScript; this means no other 3rd party libraries, like $async
to automatically trigger the minimum number of digest cycles
How was this achieved?
In ES6 we've received an awesome featured called Proxy. This object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).
This means that we can wrap the Promise into a Proxy which, when the promise gets resolved or rejected, triggers a digest cycle, only if needed. Since we need a way to trigger the digest cycle, this change is added at AngularJS run time.
function($rootScope) {
function triggerDigestIfNeeded() {
// $applyAsync acts as a debounced funciton which is exactly what we need in this case
// in order to get the minimum number of digest cycles fired.
$rootScope.$applyAsync();
};
// This principle can be used with other native JS "features" when we want to integrate
// then with AngularJS; for example, fetch.
Promise = new Proxy(Promise, {
// We are interested only in the constructor function
construct(target, argumentsList) {
return (() => {
const promise = new target(...argumentsList);
// The first thing a promise does when it gets resolved or rejected,
// is to trigger a digest cycle if needed
promise.then((value) => {
triggerDigestIfNeeded();
return value;
}, (reason) => {
triggerDigestIfNeeded();
return reason;
});
return promise;
})();
}
});
}
Since async functions rely on Promises to work, the desired behavior was achieved with just a few lines of code. As an additional feature, one can use native Promises into AngularJS!
Later edit: It's not necessary to use Proxy as this behavior can be replicated with plain JS. Here it is:
Promise = ((Promise) => {
const NewPromise = function(fn) {
const promise = new Promise(fn);
promise.then((value) => {
triggerDigestIfNeeded();
return value;
}, (reason) => {
triggerDigestIfNeeded();
return reason;
});
return promise;
};
// Clone the prototype
NewPromise.prototype = Promise.prototype;
// Clone all writable instance properties
for (const propertyName of Object.getOwnPropertyNames(Promise)) {
const propertyDescription = Object.getOwnPropertyDescriptor(Promise, propertyName);
if (propertyDescription.writable) {
NewPromise[propertyName] = Promise[propertyName];
}
}
return NewPromise;
})(Promise) as any;
In case you're upgrading from AngularJS to Angular using ngUpgrade (see https://angular.io/guide/upgrade#upgrading-with-ngupgrade):
As Zone.js patches native Promises you can start rewriting all $q based AngularJS promises to native Promises, because Angular triggers a $digest automatically when the microtask queue is empty (e.g. when a Promise is resolved).
Even if you don't plan to upgrade to Angular, you can still do the same, by including Zone.js in your project and setting up a similar hook like ngUpgrade does.
Is there any workaround so I don't have to manually call $scope.$apply() every time?
This is because TypeScript uses the browser native Promise implementation and that is not what Angular 1.x knows about. To do its dirty checking all async functions that it does not control must trigger a digest cycle.
As #basarat said the native ES6 Promise doesn't know about the digest cycle. You should to promise
async testAsync() {
await this.$timeout(2000).toPromise()
.then(response => this.text = "Changed");
}
As it already has been described, angular does not know when the native Promise is finished. All async functions create a new Promise.
The possible solution can be this:
window.Promise = $q;
This way TypeScript/Babel will use angular promises instead.
Is it safe? Honestly I'm not sure - still testing this solution.
I would write a converter function, in some generic factory (didnt tested this code, but should be work)
function toNgPromise(promise)
{
var defer = $q.defer();
promise.then((data) => {
$q.resolve(data);
}).catch(response)=> {
$q.reject(response);
});
return defer.promise;
}
This is just to get you started, though I assume conversion in the end will not be as simple as this...

When do $q promises resolve

I have a following piece of code:
function getData(foos, bars) {
var generated = {};
return $q(function(resolve, reject) {
var promises = _.map(foos, function(foo) {
return _.map(bars, function(bar) {
return someServiceCall(foo, bar).then(function(data) {
_.set(generated[foo.id], player.id.toString() + '.data', data);
});
});
});
// Join all promises in to a final resolve
$q.all(promises).then(function() {
resolve(generated);
}, reject);
});
}
What I want to achieve is to have all the someServiceCall-s and it's success handlers finished by the time resolve(generated) is called. When I debug this part of the code in the developer toolbar, resolve(generated) is called before the success handler of someServiceCall is called for every promise.
Note: This not always breaks the functionality, as the objects are passed as a reference, so the data is set even if resolve was already called, but I think the functionality would be clearer if all those calls were finished by the time resolve is called.
I just realized my dumb mistake. The two nested maps resulted the following structure:
var promises = [[Promise, Promise][Promise, Promise, Promise]];
While $q.all expects an array of promises:
var promises = [Promise, Promise, Promise, Promise, Promise];
I could easily solve this by replacing the first map call by flatMap:
return _.flatMap(bars, function(bar) {
It's still strange for me that $q.all silently just resolved the promise without an error or warning that the data format is not appropriate.
I hope I can help someone who runs into this problem in the future.

Using the $q service with angular

I still can't understand the role of using the $q service, (what exactly will it add) if you want to create a service that need to call only one API via http , in this situation I don't know why shouldn't I just do the following (without using $q) :
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie)
.then(
function(response) {
return {
title: response.data.title,
cost: response.data.price
});
},
function(httpError) {
// translate the error
throw httpError.status + " : " +
httpError.data;
});
};
Very good question and one very few people appreciate the answer to.
Consider this:
this.getMovie = function(movie) {
return $http.get('/api/v1/movies/' + movie);
};
Great code but these rules apply:
$http will resolve for 2xx responses and will otherwise reject. sometimes we don't want this. we want to reject on resolution and resolve on rejection. This makes sense when you consider a HEAD request to check the existence of something.
A HEAD request to /book/fred returning 200 shows book fred exists. But if my function is testing whether book fred is unique, it is not and so we want to reject on a 200. this is where $q comes in. Now I can do this:
var defer = $q.defer();
$http.head('/book/fred').then(function(response) {
// 2xx response so reject because its not unique
defer.reject(response);
}).catch(function(failResponse) {
defer.resolve(failResponse);
});
return defer.promise;
$q gives me total control of when I reject AND when I resolve.
Also, $q allows me to reject or resolve with any value. So if I am only interested in some of the response I can resolve with just the bit I am interested in.
Finally, I can use $q to turn non-promise based code into a promise.
var a = 5;
var b = 10;
var defer = $q.defer();
var resolve(a+b);
return defer.promise;
Bosh, if I need a promise as my return value then I've got one.
This is also great when mocking for unit tests.
AngularJS services such as $http, $timeout, $resource, etc. use the $q service internally to generate their promises. With those services there generally is no need to inject the $q service. In fact if you see $q.defer being used with those services, you should be asking Is this a “Deferred Antipattern”?.
There are some methods of the $q service that are useful in certain circumstances.
The $q.all method is used to wait on several promises.
var promise1 = $http.get(url1);
var promise2 = $http.get(url2);
$q.all([promise1, promise2]).then( responseArray ) {
$scope.data1 = responseArray[0].data;
$scope.data2 = responseArray[1].data;
}).catch( function ( firstError ) {
console.log(firstError.status)
});
The .catch method can be used to convert a rejected promise to a fulfilled promise. And vice versa with the .then method. No need to use $q.defer for that. For more information, see Angular execution order with $q.
The $q.when method is useful for generating a promise from unknown sources.
var promise = $q.when(ambiguousAPI(arg1));
The $q.when method creates a $q promise in all cases whether ambiguousAPI returns a value, a $q promise, or a promise from another library.
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.1
To summarize: The $q service is used to create a promise, so when using service (like $http,$timeout,$resource,etc.) that already return promises you generally don't need to use the $q service.
In this case you certainly don't need it because $http.get itself returns a promise. But for example if you perform async call only on some condition it is useful
function acyncService () {
if (dataLoaded) return $q.resolve(data);
return $http.get('path/to/load/data');
}
In this case even if you do not perform async call, you still can use
acyncService().then(function(data){
console.log(data);
});
This is only one of many examples. It is also useful to use $q promises when you do async requests with other libs like AWS SDK for instance.
$http does an asynchronous call. So, ideally if you want to get the value of the response in the url, you should use a '$scope' variable to store it.
$http("your url").then(function(response){
//This is the success callback
$scope.movies = response.data;
});

angularJS $http request 'how to sync'

I think you find this question thousands of times...but I can't really understand the way to solve.
I have a $http request inside a Service...
app.service('getData', function ($http) {
this.getDataList = function () {
$http.get('../content/catalog/data.json')
.success(function(response) {
return response;
})
};
I call it from the app.run
app.run(function (getData) {
list=getData.getDataList()
})
If I log the list variable is undefined
What is the way to sync them?
Thank you for the help!!!!
You're treating the call as if it were synchronous, where as its an Async call, so when you say return response; that line executes when the call hasn't finished yet and naturally you get undefined, use a callback instead or return a promise:
this.getDataList = function (callback) {
$http.get('../content/catalog/data.json')
.success(function(response) {
callback(response);
})
};
Usage:
getData.getDataList(function(data){
var list=data;
console.log(data);
});
EDIT:
Regarding promises, the idea is very simple, whenever you have an async operation that you expect will not be completed immediately, i.e. ajax calls, you can use a promise returned by the method making that async operation in order to find out when the task has finished.
For example $http returns a promise by default, so you can change your code to make use of that promise by simply returning the $http call itself:
this.getDataList = function () {
return $http.get('../content/catalog/data.json');
}
and then use it like this:
getDataList().then(function(successData){
var list=successData;
},function(errorResponse){
alert("something terrible has happened!");
})
The promise returned by $http takes 2 callbacks, the first for a successful call and the second is for errors.
Nowadays I mostly just pass callbacks in to the function making the async call, saves me from having to write then().
list is undefined because you forgot to return $http.get in this.getDataList()
By the way, $http will return a promise (an object with then and finally methods), which you can directly use in ng-bind for instance.
Use promises instead.
JavaScript uses promises for async(deferred) operations. These promises are based on callbacks(I promise to run your callback when I'm done).
getDataList does a http.get behind the scenes and doesn't block the code. It simply returns a promise object.
You can add callbacks to the promise object that will happen when the async operation finishes.
app.service('getData', function ($http) {
this.getDataList = function () {
return $http.get('../content/catalog/data.json');
};
});
app.run(function (getData) {
getData.getDataList().then(function(res){
list = res
});
});

Resources