Detect existence of next handler in Angular JavaScript promise chain - javascript

Given the following two $resource examples:
var exampleOne = $resource('/path').save(objectOne);
exampleOne.$promise.then(function (success) {}, function (error) {});
var exampleTwo = $resource('/path').save(objectTwo);
exampleTwo.$promise.then(function (success) {});
[NOTE: Example two contains no error handler]
And an interceptor that sits below all $http requests:
var interceptor = ['$location', '$q', function ($location, $q) {
function error(response) {
if (response.status === 400) {
return $q.reject(response);
}
else {
$location.path('/error/page');
}
return $q.reject(response);
}
return {
'responseError': error
};
}
$httpProvider.interceptors.push(interceptor);
How can I make the interceptor not reject when the example resources $promise.then() contain no error callback? If the call back exists as in exampleOne then I wish to reject, but if not as in exampleTwo then I wish to redirect to the error page thus changing the conditional to something like:
if (response.status === 400 && $q.unresolvedPromises.doIndeedExist()) { ...
Why? Because only some situations in my project call for handling a 400 in a user friendly way, thus I'd like to eliminate many duplicate error callbacks or having to place a list of uncommon situations in the interceptor. I'd like the interceptor to be able to decide based on the presence of another handler in the promise chain.

Simply put it is impossible, you can't detect if someone will attach a handler in some point in the future just like you can't tell if when you throw in a function it will be caught on the outside or not. However, what you want done can be done.
It is not a 'noob question', and it is very fundamental:
function foo()
throw new Error(); // I want to know if whoever is calling `foo`
// handles this error
}
First, what you can do
Simply put in the first case:
exampleOne.$promise.then(function (success) {}, function (error) {});
What you get is a promise that is always fulfilled. However, in the second case the promise might be rejected. Handling a rejection with a rejection handler is like a catch in real code - once you handle it it is no longer rejected.
Personally, I would not use an interceptor here, but rather a resource-using pattern since that's more clear with intent, you can wrap it in a function so it won't need a scope but I like that idea less. Here is what I'd do
attempt(function(){
return $resource('/path').save(objectTwo).$promise.
then(function (success) {});
});
function attempt(fn){
var res = fn();
res.catch(function(err){
// figure out what conditions you want here
// if the promise is rejected. In your case check for http errors
showModalScreen();
}
return res; // for chaining, catch handlers can still be added in the future, so
// this only detects `catch` on the function passed directly so
// we keep composability
}
Now, a short proof that it can't be done
Let's prove it for fun.
Let's say we are given the code of a program M, we create a new promise p and replace every return statement in M andthrow statement in M with a return p.catch(function(){}) and also add a return p.catch(function(){}), now a handler will be added to p if and only if running M ever terminates. So in short - given code M we have constructed a way to see if it halts based on an existence of a solution to the problem of finding if catch is appended to p - so this problem is at least as hard as the halting problem.

Maybe you can postpone redirect with zero timeout and give a chance to error handler if any exists to set flag on error object that error was handled:
var interceptor = ['$q', '$timeout', function ($q, $timeout) {
function error(rejection) {
return $q.reject(rejection).finally(function () {
$timeout(function () {
if (rejection.errorHandled === true) {
alert('all is under control');
} else {
alert("Houston we've got problems");
}
}, 0); //zero timeout to execute function after all handlers in chain completed
});
}
return {
'responseError': error
};
}];
var exampleOne = $resource('/path').save(objectOne);
exampleOne.$promise.then(function (success) { }, function(error) {
error.errorHandled = true;
});

Related

Why do JavaScript promises chain rejections into resolved promises?

I'm wondering why rejections are chained as resolved with ES6 promises?
Here's an example:
let p = Promise.reject().then(function () {
return 'one';
}, function() {
return 'two';
});
p.then(function (value) {
// I do not expect this to be executed, since it was not resolved.
console.log(value);
});
The above outputs "two" to the console.
https://jsfiddle.net/pscb88xg/1/
Why does the chaining of a promise mutate a rejection into a successful resolve?
Edit: I want to clarify that the question has practical application.
What if you want to convert data from A to B using chaining.
p.then(function (A) {
return new B(A);
});
The above mutates rejections into resolved values. Even if no reject callback is used.
For example;
let p = Promise.reject('error').then(function (A) {
return new B(A);
});
// some code elsewhere
p.then(function (B) {
console.log('success!');
});
In the above example. The value B is not B but the error, and it was resolved successfully later in the chain.
Is this normal?
Edit: I understand my confusion now. I was extracting HTTP header values in rejections like this.
let p = myHttpService.get(...).then(function() {
//....
}, function(response) {
// get headers
});
The above was chaining my promises to a resolved value, and I didn't understand why. I can fix my code with the following.
let p = myHttpService.get(...).then(function() {
//....
}, function(response) {
// get headers
return Promise.reject(response);
});
After handling an error you usually want your code to continue, similar to how code after a catch block runs like normal, whereas uncaught exceptions abort.
If you want to abort instead then don't handle the error until the end of the chain:
let p = Promise.reject().then(function () {
return 'one';
});
p.then(function (value) {
// This won't run, because the rejection hasn't been handled yet
console.log(value);
}, function() {
return console.log( 'there was a problem' );
}).then(function ( ) {
// This will run because the rejection has been dealt with already.
console.log( 'Moving on');
});
MDN documentation for Promise.prototype.then says:
After the invocation of the handler function [the function passed to then()], the promise returned by then gets resolved with the returned value as its value.
It's meant to allow you to gracefully recover from an error in a promise chain.
An example might be the 304 Not Modified response from the server. If you were to use a promise based library to do an http request any response that's not 2XX will be considered a failure and the promise will be rejected. From an application's point of view however 304 might just as good as a 200 and you'd like to continue as normal.
This is the same behavior as AngularJS's $q provider.
The mutation occurs because in your rejection handler, you are returning a value and not a rejected promise. If you were to instead, pass a rejected promise, it would behave how you were expecting:
let p = Promise.reject().then(function () {
return 'one';
}, function() {
return Promise.reject('two');
});
p.then(function (value) {
// I do not expect this to be executed, since it was not resolved.
console.log(value);
}, function() {
console.log("Rejected, baby!");
});

Is there a shortcut to define and return a rejected promise? [duplicate]

My scenario
I used to have some node.js implementation done using callbacks but I am now refactoring my code to use Promises instead - using Q module. I have the following update() function where the inner _update() function already returns a Promise:
exports.update = function(id, template, callback) {
if (!_isValid(template)){
return callback(new Error('Invalid data', Error.INVALID_DATA));
}
_update(id, template) // this already returns a promise
.then(function() {
console.log('UPDATE was OK!');
callback();
}, function(err) {
console.log('UPDATE with ERRORs!');
callback(err);
});
};
My question
I would like to achieve something like the following:
exports.update = function(id, template) {
if (!_isValid(template)){
// how could I make it return a valid Promise Error?
return reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template) // return the promise
.done();
};
Because _update() already returns a promise, I guess changing it this way would be enough (wouldn't be?):
return _update(id, template)
.done();
And... what about if the condition inside the if-clause equals true? How could I refactor
return callback(new Error('Invalid data', BaboonError.INVALID_DATA));
to throw an error to avoid passing the callback into update() and handling that error (or what ever error could ever be returning _update())?
Also, calling update():
myModule.update(someId, someTemplate)
.then(function() { /* if the promise returned ok, let's do something */ })
.catch(function(err) { /* wish to handle errors here if there was any */});
somewhere else in my code:
if there is an error during the promise propagation - it should handle it,
or, if there wasn't an error - it should do some other things
Am I close to what I am expecting? How could I finally achieve it?
I see only two problems.
If you want to explicitly return a rejected promise with a value, you should do that with Q.reject.
Calling .done() on promise means that the promise ends there. It cannot be chained further.
So, your code would look like this
exports.update = function (id, template) {
if (!_isValid(template)) {
return Q.reject(new Error('Invalid data', Error.INVALID_DATA));
}
return _update(id, template);
};
Now, the update function just returns a promise always. Its up to the callers to attach the success or failure handlers to it.

Parse.Query.each() chained promises

I'm writing a background job function on Parse.com CloudCode. The job needs to call the same function (that includes a Parse.Query.each()call) several times with different parameters, and I want to chain these calls with promises. Here's what I have so far:
Parse.Cloud.job("threadAutoReminders", function(request, response) {
processThreads(parameters1).then(function() {
return processThreads(parameters2);
}).then(function() {
return processThreads(parameters3);
}).then(function() {
return processThreads(parameters4);
}).then(function() {
response.success("Success");
}, function(error) {
response.error(JSON.stringify(error));
});
});
Below is the processThreads() function:
function processThreads(parameters) {
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
return threadQuery.each(function(thread) {
console.log("Hello");
// do something
});
}
My questions are:
Am I chaining function calls using promises correctly?
What happens in threadQuery.each() returns zero results? Will the promise chain continue with execution? I'm asking because at the moment "Hello" never gets logged..
Am I chaining function calls using promises correctly?
Yes.
What happens in threadQuery.each() returns zero results? Will the promise chain continue with execution? I'm asking because at the moment "Hello" never gets logged.
I think I'm right in saying that, if "do something" is synchronous, then zero "Hello" messages can only happen if :
an uncaught error occurs in "do something" before a would-be "Hello" is logged, or
every stage gives no results (suspect your data, your query or your expectation).
You can immunise yourself against uncaught errors by catching them. As Parse promises are not throw-safe, you need to catch them manually :
function processThreads(parameters) {
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
return threadQuery.each(function(thread) {
console.log("Hello");
try {
doSomething(); // synchronous
} catch(e) {
//do nothing
}
});
}
That should ensure that the iteration continues and that a fulfilled promise is returned.
The following example shows as use promises inside your function using a web browser implementation.
function processThreads(parameters) {
var promise = new Promise();
var threadQuery = new Parse.Query("Thread");
threadQuery... // set up query using parameters
try {
threadQuery.each(function(thread) {
console.log("Hello");
if (condition) {
throw "Something was wrong with the thread with id " + thread.id;
}
});
} catch (e) {
promise.reject(e);
return promise;
}
promise.resolve();
return promise;
}
Implementations of promise:
Web Browser https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise
jQuery https://api.jquery.com/promise/
Angular https://docs.angularjs.org/api/ng/service/$q

Best practice to handle exception when using Q.promise

I have the following method:
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
resolve(myId);
});
};
This method works great as long as someObject exists and has the attributes user.player._id.
The problem i'm having is that if someObject is null or does not have all the appropriate nested attributes, an exception is thrown and the promise is never resolved. The only way I actually see the exception is if I have a .fail on the calling function, but that still doesn't actually resolve the promise.
Example of how I currently can see the exception:
myLib.getId.then(function() {
// something
}).fail(function(err) {
console.log(err);
});
I know 2 ways to get around this problem, but i'm not sure which, if either is the best way to handle something like this.
Option 1 (use try/catch inside my Q.promise):
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
try {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
} catch(e) {
reject(e);
}
resolve(myId);
});
};
Option 2 (explicitly check if someObject.user.player._id exists):
module.exports.getId = function(someObject) {
var myId = null;
return Q.Promise(function(resolve, reject, notify) {
ifi(someObject.user.player._id exists..) {
// Loop through all the id's
someObject.user.player._id.forEach(function (id) {
if (id.root == "1.2.3.4.5.6") {
myId = id.extension;
}
});
resolve(myId);
} else {
reject('invalid object');
}
});
};
Option 1 seems to smell funky to me because i'm using try/catch inside of a promise. Option 2 solves my problem, but any other unexpected exceptions will not get caught.
Is there a better way I should be handling this?
Your first example has a few problems:
When you catch an exception, you are rejecting the promise, then resolving the promise. That's breaking the promise contract; You can get around that by calling resolve within the try, not outside.
By using try/catch, you could be swallowing unintended errors. That is you are assuming that the only error would come from someObject.user.player._id not existing. That may be true at the moment, but it's not guaranteed to remain true as your code evolves.
By testing exactly for the known error condition, you know you won't be swallowing unexpected errors. Therefore, I would use your second example.

Angular - Bind to function that returns a promise

I am new to angular, and I am having a tough time getting to the bottom of this problem.
I am writing a single-page application, and am working on the authentication portion. I have a service called "sessionService" that I want to be able to use throughout the app to determine if the user is logged in or not. It is simple if I do something like this:
...service('sessionService', function(...) {
/*...snip...*/
this.isLoggedIn = function() {
return this.authenticated;
};
});
Where "authenticated" is just private to the service. However, the falls apart if I refresh the page. So, my thought was to do something like this:
/*...snip...*/
this.isLoggedIn = function() {
var deferred = $q.defer()
, self = this
;
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
deferred.resolve();
}
else {
deferred.reject();
}
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
});
}
else {
handleLoggedInStatus(this.authenticated);
}
return deferred.promise;
};
And then in my controller I would do something like this:
$scope.isLoggedIn = sessionService.isLoggedIn;
And in my template I would do:
...data-ng-show="isLoggedIn()"
However, doing that would result in the following error:
10 $digest() iterations reached. Aborting!
I tried a few different ways of referencing the sessionService.isLoggedIn function, such as:
$scope.isLoggedIn = sessionService.isLoggedIn();
$scope.isLoggedIn = sessionService.isLoggedIn.bind(sessionService)();
$scope.isLoggedIn = function() { return sessionService.isLoggedIn() }
But they either didn't work, or just gave me the same error.
Basically, I just want to be able to return a promise that will tell me whether or not the user is logged in. If we don't know if they are logged in (like after a page refresh), the promise will be resolved after an ajax request. If we do know already (like with normal navigation throughout the single page app) then the promise will be resolved immediately. I would then like to use that in my views so I can show/hide certain things, such as links to logout or view the account page.
What am I doing wrong?
You're resolving your promise, but not with a value--so the value of the promise on the $scope when resolved is undefined, which is falsy, thus your ng-show is not triggering.
It seems you're looking for something more like this:
In the service:
function handleLoggedInStatus(status) {
if (status) {
self.authenticated = true;
}
deferred.resolve(status); // always resolve, even if with falsy value
}
if (this.authenticated === null) {
$http.get('/user')
.success(function(response) {
handleLoggedInStatus(response.success);
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
} else {
handleLoggedInStatus(this.authenticated);
}
In the controller:
$scope.loggedIn = sessionService.isLoggedIn();
In the HTML:
<div ng-show='loggedIn'>...</div>
Here is a JSFiddle demonstrating resolving the deferred with a truthy value and binding to the $scope.
Note that you can't bind the function itself to the scope
$scope.loggedIn = sessionService.isLoggedIn
and call the function in the view
<div ng-show="loggedIn()">...</div>
because the function returns a different promise each digest cycle (which is why you were getting the '10 digest cycles' error). You could, however, ensure that extra calls to sessionService.isLoggedIn returns the same promise instead of creating a new one, since you can call then on a promise multiple times (and in fact this is one of the benefits of promises):
deferred = null;
isLoggedIn: function() {
if (!deferred) {
deferred = $q.defer();
$http.get('/user')
.success(function(response) {
deferred.resolve(response.success); // resolve if true or false
})
.error(function(data) {
deferred.reject(data.errorMsg); // reject if there was an error
});
}
return deferred.promise;
}
You could then get rid of the this.authenticated boolean, as you do not need to keep track of a previously-logged-in user across function calls (since the promise does this for you).
However, while this gets rid of the digest cycle error, you still cannot call the function from the view--I suspect Angular is treating the return value (the promise itself) as a truthy value, rather than binding to the promise's resolved value. Here's an example of it not working; notice the div is displayed even though the promise is resolving with false.
To use deferred.reject to indicate the user was not authenticated, as in your original service, you'd want to do something more like this in the controller, though I believe that resolveing with false is cleaner:
sessionService.isLoggedIn()
.then(function() {
$scope.loggedIn = true; // resolved
}).then(function() {
$scope.loggedIn = false; // rejected
});

Categories

Resources