What $q.defer() really does? - angularjs

I'm learning about Angular JS and on the moment I'm trying to understand about promises and async programming and I have this doubt about $q.defer(). My point is the following: usually when people work with promises they do something like that, considering that $q is already available
function someAsyncFunction() {
var deferred = $q.defer();
/* Do things and if everything goes fine return deferred.resolve(result)
otherwise returns deferred.reject()
*/
return deferred.promise;
}
What is this really doing? When we do var deferred = $q.defer() it imediately switches all the execution of that function to another thread and return the promise being a reference to the results of this operation that is still performing there?
Is this the way we should think about when creating async methods?

With $q u run functions asynchronously.
Deferred objects signals that something, some task is done.
var defer = $q.defer(); // we create deferred object, which will finish later.
defer.promise // we get access to result of the deferred task
.then( // .then() calls success or error callback
function(param) {
alert("i something promised " + param);
return "something";
}); // u can use 1 or more .then calls in row
defer.resolve("call"); //returns promise
Here example:
http://jsfiddle.net/nalyvajko/HB7LU/29048/

Angular's $q service is based on the Javascript library Q. You can read more about it in the Q documentation, or read the code in the github repo. I think this part snipped from the introduction to the documentation explains it best:
If a function cannot return a value or throw an exception without
blocking, it can return a promise instead. A promise is an object that
represents the return value or the thrown exception that the function
may eventually provide. A promise can also be used as a proxy for a
remote object to overcome latency.

Related

Catched errors in AngularJS's exception handler [duplicate]

In the following code, an exception is caught by the catch function of the $q promise:
// Fiddle - http://jsfiddle.net/EFpn8/6/
f1().then(function(data) {
console.log("success 1: "+data)
return f2();
})
.then(function(data) {console.log("success 2: "+data)})
.catch(function(data) {console.log("error: "+data)});
function f1() {
var deferred = $q.defer();
// An exception thrown here is not caught in catch
// throw "err";
deferred.resolve("done f1");
return deferred.promise;
}
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
However when I look in the console log output I see the following:
The exception was caught in Angular, but was also caught by the error handling of the browser. This behavior does reproduce with Q library.
Is it a bug? How can I truly catch an exception with $q?
Angular's $q uses a convention where thrown errors are logged regardless of being caught. Instead, if you want to signal a rejection you need to return $q.reject(... as such:
function f2() {
var deferred = $q.defer();
// An exception thrown here is handled properly
return $q.reject(new Error("err"));//throw "err";
deferred.resolve("done f2");
return deferred.promise;
}
This is to distinguish rejections from errors like SyntaxError. Personally, it's a design choice I disagree with but it's understandable since $q is tiny so you can't really build in a reliable unhandled rejection detection mechanism. In stronger libraries like Bluebird, this sort of thing is not required.
As a side note - never, ever throw strings : you miss on stack traces that way.
Is it a bug?
No. Looking in the source for $q reveals that a deliberate try / catch block is created to respond to exceptions thrown in the callback by
Rejecting the promise, as through you had called deferred.reject
Calling the registered Angular exception hander. As can be seen in the $exceptionHandler docs, the default behaviour of this is to log it to the browser console as an error, which is what you have observed.
... was also caught by the error handling of the browser
To clarify, the exception isn't handled directly by the browser, but appears as an error because Angular has called console.error
How can I truly catch an exception with $q?
The callbacks are executed some time later, when the current call stack has cleared, so you won't be able to wrap the outer function in try / catch block. However, you have 2 options:
Put in try/catch block around the code that might throw the exception, within the callback:
f1().then(function(data) {
try {
return f2();
} catch(e) {
// Might want convert exception to rejected promise
return $q.reject(e);
}
})
Change how Angular's $exceptionHandler service behaves, like at How to override $exceptionHandler implementation . You could potentially change it to do absolutely nothing, so there would never be anything in the console's error log, but I don't think I would recommend that.
Fixed with AngularJS version 1.6
The reasoning for this behavior was that an uncaught error is different than a regular rejection, as
it can be caused by a programming error, for example. In practice, this turned out to be confusing
or undesirable for users, since neither native promises nor any other popular promise library
distinguishes thrown errors from regular rejections.
(Note: While this behavior does not go against the Promises/A+ spec, it is not prescribed either.)
$q:
Due to e13eea, an error thrown from a promise's onFulfilled or onRejection handlers is treated exactly the same as a regular rejection. Previously, it would also be passed to the $exceptionHandler() (in addition to rejecting the promise with the error as reason).
The new behavior applies to all services/controllers/filters etc that rely on $q (including built-in services, such as $http and $route). For example, $http's transformRequest/Response functions or a route's redirectTo function as well as functions specified in a route's resolve object, will no longer result in a call to $exceptionHandler() if they throw an error. Other than that, everything will continue to behave in the same way; i.e. the promises will be rejected, route transition will be cancelled, $routeChangeError events will be broadcasted etc.
-- AngularJS Developer Guide - Migrating from V1.5 to V1.6 - $q
The deferred is an outdated and a really terrible way of constructing promises, using the constructor solves this problem and more:
// This function is guaranteed to fulfill the promise contract
// of never throwing a synchronous exception, using deferreds manually
// this is virtually impossible to get right
function f1() {
return new Promise(function(resolve, reject) {
// code
});
}
I don't know if angular promises support the above, if not, you can do this:
function createPromise(fn) {
var d = $q.defer();
try {
fn(d.resolve.bind(d), d.reject.bind(d));
}
catch (e) {
d.reject(e);
}
return d.promise;
}
Usage is same as promise constructor:
function f1() {
return createPromise(function(resolve, reject){
// code
});
}
Here is an sample test that shows the new $q construction function, use of .finally(), rejections, and promise chain propagations:
iit('test',inject(function($q, $timeout){
var finallyCalled = false;
var failValue;
var promise1 = $q.when(true)
.then(function(){
return $q(function(resolve,reject){
// Reject promise1
reject("failed");
});
})
.finally(function(){
// Always called...
finallyCalled = true;
// This will be ignored
return $q.when('passed');
});
var promise2 = $q.when(promise1)
.catch(function(value){
// Catch reject of promise1
failValue = value;
// Continue propagation as resolved
return value+1;
// Or continue propagation as rejected
//return $q.reject(value+2);
});
var updateFailValue = function(val){ failValue = val; };
$q.when(promise2)
.then( updateFailValue )
.catch(updateFailValue );
$timeout.flush();
expect( finallyCalled ).toBe(true);
expect( failValue ).toBe('failed1');
}));

Difference between Sequential and Parallel executing in $q

In both of at the time of execution of an asynchronic operation only.
But how the $q handle for this is a Sequential call or Parallel call on runtime?
and let me brief explanation about the Difference between Sequential and Parallel executing in angular $q
Parallel Execution is something in which it doesn't wait for the previous process to be done,and Sequential is something in which process are executed one after another.
$q service is used for asynchronous call (parallel execution | promise handling),by default it does parallel execution,it does not have support for sequential execution.
If you want a sequential execution you have to handle it manually, wheich means after getting response from one call you make another call.
var promise;
promise.then(fun(){
var promise;
promise.then(fun(){
})
})
To execute promises in parallel:
var promise1 = promiseAPI(params1);
var promise2 = promiseAPI(params2);
var promise1and2 = $q.all([promise1, promise2]);
To execute promises sequentually, return the next promise to the first promise success handler:
var promise1 = promiseAPI(params1);
var promise1then2 = promise1.then(function() {
var promise2 = promiseAPI(params2);
//return to chain
return promise2;
});
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.
-- AngularJS $q Service API Reference -- Chaining Promises.

$q.defer() creating a resolved promise

looking for some help on an issue that makes me losing my hair ! :)
I have to send a series of call to an API I consume. so I have created a factory with a function like
addItem : function(){
var deferred=$q.defer();
//call to the API
.then(function(response){
deferred.resolve(process(response.data));
}, function(response){
deferred.reject(errorManagement(response.status));
});
}
return deferred.promise;
}
Then I have built an array with code:
for(var i=0; i<nbOfElements; i++) {
arrayOfPromises[i]=Factory.addItem();
}
$q.all(arrayOfPromises).then(..)
My expectation is that the $q.all will resolve only when all calls to the API have been completed.
Unfortunately, it appears that this is not the case, and then I display a partial result, not satisfactory
after some debugging, it appears that the promises returned by the factory all have $$state.status = 1, which seems to be "resolved" state, explaining why the $q.all resolved before I would like.
(link to the values of $$state.status)
Still I find this weird as I have used this $q.defer() a lot, but without $q.all and it always worked fine
Any explaination on this issue and how to solve it would be much welcome :)
Simply return the $http.get() promise will actually work: JSFiddle.
If you want to do some pre-processing in the factory before returning the data (like your process and errorManagement), check demo: JSFiddle.
The $q.all callback function not invoked only after all promises are resolved. It is not because of $q.all.
Your array code has problem: arrayOfPromises[i]=Factory.addItem();. You'd better use push to expand the array.
Maybe I can't solve your problem, just giving suggestions:
You can save a lot of code by returning the promise of your API call instead of creating new promises (this is one of the promise anti-patterns)
addItem: function() {
//call to the API
return $http(something).then(function(response){
return process(response.data);
// if you happen need to reject here, use $q.reject(reason/data);
}, function(response){
return $q.reject(errorManagement(response.status));
});
}
I'm more comfortable on assigning into an array using push
var arrayOfPromises = [];
for(var i=0; i<nbOfElements; i++) {
arrayOfPromises.push(Factory.addItem());
}
$q.all(arrayOfPromises).then(..)
You are not missing the part before .then in the addItem function in your real code, are you?

Optional first promise in Angular chain

I have 2 $http calls that return promises but the first one is optional. I believe that I have to first create a promise using $q.defer() but I am missing something.
Here's my non working attempt:
var p = $q.defer();
if (condition) {
p = p.then(doOptionalFirst());
}
return p.then(doOther());
What is correct syntax to chain these 2 calls with the first being optional?
Use $q.when (or $q.resolve with AngularJS 1.4.1) to create an already resolved promise.
var p = $q.resolve();
if (condition) {
p = p.then(doOptionalFirst);
}
return p.then(doOther);
If you are using a deferred, you have to chain to the .promise and then resolve the deferred at an appropriate time. In this case you can consider that if condition is true the deferred is automatically resolved. Thus you can skip some extra possibly confusing code by just using an already resolved promise.

Why $scope.apply matters for angular's $q?

I am following the help on AngularJS's documentation for Q's implementation $q. I tried out the following code from https://docs.angularjs.org/api/ng/service/$q
// for the purpose of this example let's assume that variables `$q`, `scope` and `okToGreet`
// are available in the current lexical scope (they could have been injected or passed in).
function asyncGreet(name) {
var deferred = $q.defer();
setTimeout(function() {
// since this fn executes async in a future turn of the event loop, we need to wrap
// our code into an $apply call so that the model changes are properly observed.
scope.$apply(function() {
deferred.notify('About to greet ' + name + '.');
if (okToGreet(name)) {
deferred.resolve('Hello, ' + name + '!');
} else {
deferred.reject('Greeting ' + name + ' is not allowed.');
}
});
}, 1000);
return deferred.promise;
}
var promise = asyncGreet('Robin Hood');
promise.then(function(greeting) {
alert('Success: ' + greeting);
}, function(reason) {
alert('Failed: ' + reason);
}, function(update) {
alert('Got notification: ' + update);
});
My understanding is that the $scope.apply here is to give the callback Angular's context and make sure variables under $scope are accessible.
But below on comparing $q and Kris Kowal's Q, the test code goes:
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined(); // <= so the deferred is not resolved without the 'apply'?
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
It says $rootScope.$apply() is to propagate the promise resolution to 'then'. I am confused there... So without using apply, the deferred.resolve will not actually resolve?
This documentation sucks.
So the thing with $q is that when your promise is resolved (and also presumably on reject or notify), it invokes your handlers within $rootScope.evalAsync which ensures that after invocation, it'll trigger a digest and thus the rest of your application can have a chance to update or otherwise respond to the changes, just the way we like it.
As you found out, it works just fine without the explicit $apply in an example app. However, the reason why they are doing explicit $apply is because that automagic with $evalAsync doesn't get a chance to work when running synchronously in a test, not because it's required for your application to Just Work ™.
With a few other notable services that are augmented for testing in angular-mock.js, like $http and $timeout, we can explicitly flush when we want to simulate an async http request/response or a timeout (for example). The equivalent to stuff waiting to be evaled is to trigger a digest, which will get your promise handler invoked in the proper context. This is done with $apply or $digest, and hence why you're seeing it in their examples... because their examples are written as synchronous tests.
The docs should explain the difference between what you need to do to get your tests working and what your application itself should focus on to get the job done. The docs have a bad habit of making test facts their examples, and it just confuses people.
Hope that helps.

Resources