I have already blogged about Promises in AngularJS 1.x. This is the second part which describes the Angular's $qservice. The $q service can be used in two different ways. The first way mimics the Q library for creating and composing asynchronous promises in JavaScript. The second way mimics the ECMAScript 2015 (ES6) style. Let's begin with the first way. First of all, you have to create a deferred object by $q.defer().
A deferred object can be created within an asynchronous function. The function should return a promise object created from the deferred object as follows:
A promise is always in either one of three states:
The first call deferred.resolve(...) puts the promise into the fulfilled state. As result a success callback will be invoked. The second call deferred.reject(...) puts the promise into the rejected state. As result an error callback will be invoked. It is also possible to invoke
during the function's execution to propogate some progress from the asynchronous function to an update callback. All three callbacks can be registered on the promise as parameters of the function then:
Let's implement an example. We will take setTimeout() as an asynchronous function. In the real application, you will probably use some other asynchronous services. In the setTimeout(), we will generate a random number after 1 sek. If the number is less than 0.5, we will invoke deferred.resolve(random), otherwise deferred.reject(random). The entire logic is implemented in the controller PromiseController.
As you can see, the asynchronous function asyncFunction is invoked in the controller's method invokeAsyncFunction. The function asyncFunction returns a promise. In the success case, the promise gets fulfilled and the first registered success callback gets executed. In the error case, the promise gets rejected and the second registered error callbackgets executed. The invokeAsyncFunction is bound to the onclick event on a button.
The message in GUI looks like as follows (success case as example):
The Plunker of this example is available here. Here is another picture that visualize the relation between methods of the deferred object:
As you can see, the asynchronous function doesn't return the deferred object directly. The reason for that is obvious. If the deferred object would be returned instead of deferred.promise, the caller of the asynchronous function could be able to trigger callbacks by invoking deferred.resolve(...), deferred.reject(...) or deferred.notify(...). For this reason these methods are protected from being invoking from outside by the caller.
The example above can be rewritten in the ECMAScript 2015 (ES6) style (I mentioned this way at the beginning). A promise in ECMAScript 2015 can be created as an instance of Promise object.
Our asyncFunction function looks in this case as follows:
The remaining code stays unchanged. Let's go on. The next question is, how can we produce a rejection in success or error callbacks? For instance, you check some condition in a callback and want to produce an error if the condition is not fulfilled. Sometimes, we also want to forward rejection in a chain of promises. That means, you catch an error via an error callback and you want to forward the error to the promise derived from the current promise. There are two ways to achieve this qoal. The first one consists in using the $q.reject(...) like shown below.
In our example, we will adjust the function invokeAsyncFunction in order to check very small values (smaller than 0.1).
A Plunker example is available here. Keep in mind the difference between deferred.reject(...) and $q.reject(...). The call deferred.reject(...) puts the corresponding promise into the rejected state. The call $q.reject(...) creates a new promise which is already in the rejected state.
The second way to produce a rejection in success or error callbacks consists in throwing an exception with throw new Error(...).
AngularJS will catch the exception, create a promise in the rejected state and forward it to the next block in the chain of promises. In the example, the error will be forwarded to the catch block. One downside of this approach with throw new Error(...) is that the error will be logged in the console. Picture from the Chrome Dev Tools:
But well, it works as designed and probably it is even an advantage to see thrown errors in the console.
There is also an opposite method $q.when(...) which returns an immediately resolved promise. The documentation says: $q.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." You can e.g. wrap an jQuery Deferred Object with $q.when(...) or simple write
and see Resolved with value: Finished! in the console. The alias of $q.when(value) is $q.resolve(value). This was introduced later in order to maintain naming consistency with ECMAScript 2015.
Last but not least is the method $q.all(promises) where promises is an array of multiple promises. This call returns a single promise that is resolved when all promises in the given array gets resolved.
As you can see, the result passed into the callback function is an array of two outcomes - the outcome of the first and the outcome of the second callback.
1
2
3
|
1
2
3
|
- Pending: the result hasn't been computed yet
- Fulfilled: the result was computed successfully
- Rejected: a failure occurred during computation
1
2
| deferred.resolve(...) deferred.reject(...) |
1
2
3
| deferred.notify(...) |
1
2
3
4
5
6
7
8
9
10
11
| var promise = someAsynchronousFunction(); promise.then( function (value) { // success ... }, function (reason) { // failure ... }, function (update) { // update ... }); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
| var app = angular.module( 'app' , []); app.controller( 'PromiseController' , PromiseController); function PromiseController($q) { var _self = this ; this .message = null ; var asyncFunction = function () { var deferred = $q.defer(); setTimeout( function () { var random = Math.random().toFixed(2); if (random < 0.5) { deferred.resolve(random); } else { deferred.reject(random); } }, 1000); return deferred.promise; } this .invokeAsyncFunction = function () { var promise = asyncFunction(); promise.then( function (message) { _self.message = "Success: " + message; }, function (message) { _self.message = "Error: " + message; }); } } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
| <!DOCTYPE html> < html > < head > < meta charset = "utf-8" /> < meta content = "IE=edge" http-equiv = "X-UA-Compatible" /> </ head > < body ng-app = "app" ng-controller = "PromiseController as ctrlPromise" > < div ng-bind = "ctrlPromise.message" ></ div > < p ></ p > < button ng-click = "ctrlPromise.invokeAsyncFunction()" > Invoke asynchronous function </ button > < script src = "controller.js" ></ script > </ body > </ html > |
The Plunker of this example is available here. Here is another picture that visualize the relation between methods of the deferred object:
As you can see, the asynchronous function doesn't return the deferred object directly. The reason for that is obvious. If the deferred object would be returned instead of deferred.promise, the caller of the asynchronous function could be able to trigger callbacks by invoking deferred.resolve(...), deferred.reject(...) or deferred.notify(...). For this reason these methods are protected from being invoking from outside by the caller.
The example above can be rewritten in the ECMAScript 2015 (ES6) style (I mentioned this way at the beginning). A promise in ECMAScript 2015 can be created as an instance of Promise object.
1
2
3
4
5
6
7
8
| var promise = new Promise( function (resolve, reject) { ... if (...) { resolve(value); // success } else { reject(reason); // failure } }); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| var asyncFunction = function () { return $q( function (resolve, reject) { setTimeout( function () { var random = Math.random().toFixed(2); if (random < 0.5) { resolve(random); } else { reject(random); } }, 1000); }); } |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
| var promise = someAsynchronousFunction(); promise.then( function (value) { // success ... if (someCondition) { $q.reject( "An error occurred!" ); } }, function (reason) { // failure ... }). catch ( function (error) { // do something in error case ... }); |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| this .invokeAsyncFunction = function () { var promise = asyncFunction(); promise.then( function (random) { if (random < 0.1) { return $q.reject( "Very small random value!" ); } _self.message = "Success: " + random; }, function (random) { _self.message = "Error: " + random; }). catch ( function (error) { _self.message = "Special error: " + error; }); } |
A Plunker example is available here. Keep in mind the difference between deferred.reject(...) and $q.reject(...). The call deferred.reject(...) puts the corresponding promise into the rejected state. The call $q.reject(...) creates a new promise which is already in the rejected state.
The second way to produce a rejection in success or error callbacks consists in throwing an exception with throw new Error(...).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| this .invokeAsyncFunction = function () { var promise = asyncFunction(); promise.then( function (random) { if (random < 0.1) { throw new Error( "Very small random value!" ); } _self.message = "Success: " + random; }, function (random) { _self.message = "Error: " + random; }). catch ( function (error) { _self.message = "Special error: " + error; }); } |
But well, it works as designed and probably it is even an advantage to see thrown errors in the console.
There is also an opposite method $q.when(...) which returns an immediately resolved promise. The documentation says: $q.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." You can e.g. wrap an jQuery Deferred Object with $q.when(...) or simple write
1
2
3
4
5
| $q.when( "Finished!" ).then( function handleResolve(value) { console.log( "Resolved with value: " , value); } ); |
Last but not least is the method $q.all(promises) where promises is an array of multiple promises. This call returns a single promise that is resolved when all promises in the given array gets resolved.
1
2
3
4
5
6
| var promise1 = someAsynchronousFunction1(); var promise2 = someAsynchronousFunction2(); $q.all([promise1, promise2]).then( function (result) { console.log( "Promises " + result[0] + " and " + result[1] + " finished their work successfully" ); }); |
No comments:
Post a Comment