$q.reject and handling errors in AngularJS chained promises - javascript

I'm having trouble understanding a basic concept of error handling with chaining promises.
In order to learn the rules, I have written a simple example, guessing what the result will be. But unfortunatly it doesn't behave as I though it will.
I have read multiple articles about the subject but perhaps can't I get details because of my poor english language.
Anyway, here is my code :
var promiseStart = $q.when("start");
var promise1 = promiseStart.then(function() {
return Serviceforpromise1.get();
});
var promise2 = promise1.then(function(data1)
{
return Serviceforpromise2.get(data1);
},function(error)
{
return $q.reject();
});
var promiseend = promise2.then(function(data2)
{
return data2;
},function(error)
{
return error;
});
return promiseend;
Well I know that it can be way better coded but it's just for the purpose.
Here is the code of Serviceforpromise1 function :
function Serviceforpromise1()
{
...
return $http.get(*whatever*).then(function (data){
return data;
},function(error)
{
return $q.reject();
});
}
Consider only the case of Serviceforpromise1's failure. A $q.reject is sent back to main chain so I'm waiting the error callback of "promise1 .then(" to be called and it worked as expected. I decided for the example to transfert the error to the "promise2 .then" so in this error callback I added the line return $q.reject();
But it never reached the second error callback (the "promise2 .then" one) and I don't understand why (like Serviceforpromise1, I returned a rejected promise !)
I will be happy to deeply understand what is happening here.
Thanks for your help.

Your understanding is correct, and the problem appears to lie somewhere in the way you are trying to observe this behavior (in something you haven't shown us).
If you return a rejected promise from either a success or error handler in then(), then the promise returned by then() will resolve to a rejected promise. Observe:
angular.module('app', [])
.controller('C', [
'$q',
function ($q) {
var promiseStart = $q.when("start");
var promise1 = promiseStart.then(function (value) {
console.log('Got a value:', value);
return $q.reject('Error!');
});
var promise2 = promise1.then(function (data1) {
return "Got some stuff";
}, function (error) {
console.log("Caught an error:", error);
return $q.reject('New error');
});
var promiseend = promise2.then(function (data2) {
return data2;
}, function (error) {
console.log('Caught an error:', error); // <-- this is logged to the console
return error;
});
return promiseend;
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.10/angular.min.js"></script>
<div ng-app='app' ng-controller='C'></div>
One thing to note here is that in that last handler, you are returning the error variable, and not throwing an exception or returning a rejected promise. So in this case, promiseend will successfully resolve with the value of that error variable.

Related

catch and finally functions in angular promise wont work when using $q, but standard Promise do work - what im missing?

I'd like to know why this promises implemented with Angular $q (tested on version 1.5.3) won't execute the "catch" neither "finally" promise functions if an error is thrown (it is being catched by the outer try catch in the example below). Whereas if I do the same with the "new Promise()" it will (Im testing this in the latest version of Chrome by the way).
Run the following code where you can inject $q (like a controller) to try it for yourself. You will notice how angular promise outputs the try/catch console log (and never excecutes the finally func.) whereas the standar promise properly catches the error and runs the catch() and finally() promise functions:
var angularPromise = function (data) {
var defered = $q.defer();
var promise = defered.promise;
var emptyvar = null;
if (data == "fail") {
//generate the exception
console.log("code reaches this point");
var fail = emptyvar.fakeproperty;
console.log("code will never reach this point due to the exception");
defered.reject("failed");//neither this...
}
return promise;
}
var standardPromise = function (data) {
return new Promise((resolve, reject) => {
var emptyvar = null;
if (data == "fail") {
//generate the exception
var fail = emptyvar.fakeproperty;
//in this scenario this error is thrown
//and captured by the promise catch()
//just as if I would call reject()
//which is the expected behaviour
}
});
}
try {
angularPromise("fail")
.then(
function (success) {
console.log("angularPromise: oka", success)
}
)
.catch(
function (fail) {
console.log("angularPromise: fail", fail);
}
).finally(
function (fail) {
console.log("angularPromise: 'finally' gets excecuted...");
}
);
} catch (e) {
console.log("angularPromise: exception catched with try/catch and not captured in promise 'catch'. Also 'finally' is never excecuted...");
}
try {
standardPromise("fail")
.then(
function (success) {
console.log(" standardPromise oka", success)
}
)
.catch(
function (fail) {
console.log("standardPromise: catched as expected", fail);
}
).finally(
function () {
console.log("standardPromise: 'finally' gets excecuted...");
}
);
} catch (e) {
console.log("standardPromise exception catched outside promise", e);
}
Wrapping your promise execution in a try/catch is not necessary—Angular $q's catch would catch any errors, so they would not bubble up to the catch in the try/catch.
Essentially, assuming you abstract your business logic out into a service, your service code would need to look something like this:
angular.module('app')
.factory('myService', function ($q) {
function doSomething(shouldSucceed) {
const deferred = $q.defer();
if (shouldSucceed) {
deferred.resolve('I succeeded! 😃');
} else {
deferred.reject('I failed! ☹️');
}
return deferred.promise;
}
return { doSomething };
});
Then, your controller would call that service and handle it accordingly:
angular.module('app')
.controller('MainCtrl', function ($scope, myService) {
$scope.shouldSucceed = function (shouldSucceed) {
myService
.doSomething(shouldSucceed)
.then(alertMessage)
.catch(alertMessage)
.finally(function () {
alert('FINALLY!');
});
};
function alertMessage(message) {
alert(message);
}
});
I've made a simple view that would allow you to click buttons and toggle each message:
<div ng-app="app" ng-controller="MainCtrl">
<button ng-click="shouldSucceed(false)">Fail</button>
<button ng-click="shouldSucceed(true)">Succeed</button>
</div>
I have provided a working example on CodePen of how to implement Angular's $q promises including .then, .catch, and .finally blocks.

Is it normal to throw exception from asynchronous methods?

I have some code that I want to refactor (extract server communication methods from controller to separate service).
Example:
$http.post("/mypath", someData)
.success(function(request) {
if (request.ok) {
$scope.error = "";
_refreshAppointments();
}
else {
$scope.error = request.err;
}
})
.error(function() {
$scope.error = "Error during communicating to server";
});
My current problem is errors processing (communication with old $scope). So I want to throw the exceptions instead such lines $scope.error = "Error during communicating to server";
And catch them in controller.
Is it good idea?
If you throw an error in a vanilla environment:
setTimeout(function () {
throw new Error();
}, 1);
The error just gets lost. (window.onerror will see it though)
Even if you do:
try {
setTimeout(function () {
throw new Error();
}, 1);
} catch (e) {
console.log(e);
}
You wont see the error.
You need to wrap each asynchronous event, like:
function mySetTimeout(callback, ms) {
setTimeout(wrap_in_try_catch(callback), ms);
}
mySetTimeout(function () {
throw new Error();
});
You can now catch the error in a generic error handler, but you still can't catch it in the surrounding code.
This is where Promises come in. Not all libraries do Promises the same way (or correctly) so I don't know how good your library support is.
Roughly your code will look like:
$.ajax().then(function () {
throw new Error();
}).fail(e) {
console.log("it failed!", e);
});
If instead you have:
$.ajax().then(function () {
throw new Error();
}); // throws something like: no fail callback for rejected value error
Then your global error handler will pick it up. This ensures no error can slip through the cracks and get lost.
Getting a Promise library to work with Errors in this way is not impossible but it's a little bit tricky to set up. Once you have this though, you're good. Error handling becomes a breeze.
You'll never write a try-catch again, just a bunch of .fail() handlers.
It's definitely good idea to extract REST/http requests into model/service layer and use those services from controller. Then handling failed operation would mean rejecting a corresponding promise, in this case throwing exception in promise effectively means the same.
For example this is how your service/factory could look like:
app.factory('dataService', function($http) {
return {
load: function() {
return $http.post("/mypath", someData).then(function(response) {
if (!response.data.ok) {
return throw new Error(response.request.err);
// or return $q.reject(response.request.err);
}
return response.request;
});
}
};
});
and consuming controller would deal with promise status, resolved (success) or rejected (failed/exception):
dataService.load().then(function(request) {
$scope.error = "";
_refreshAppointments();
})
.catch(function(err) {
$scope.error = err || "Error during communicating to server";
});

AngularJS throws exception when trying to saveChanges with breeze

I'm new to AngularJS and Breeze. I'm trying to save changes and have a problem with that. Here's my code:
In controller:
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}
In datacontext:
function saveSettings() {
if (manager.hasChanges()) {
manager.saveChanges() // breaks here if there are changes
.then(saveSucceeded)
.fail(saveFailed)
.catch(saveFailed);
} else {
log("Nothing to save");
return false;
};
}
The error is thrown in angular.js and it's very unhelpful TypeError: undefined is not a function I guess there is something simple I'm missing here, but can't figure out what is it.
Want to note that it does send correct data to SaveChanges method on server, but the error is thrown before any response from the server received. After the response is received it throws another error TypeError: Cannot read property 'map' of undefined but it might be related to the fact the response I return is invalid. I haven't got to that part yet.
Can anyone anyone help with it? I'm lost here.
UPDATE
Here is how I construct my dataservice and manager:
var serviceName = "http://admin.localhost:33333/api/breeze/"; //
var ds = new breeze.DataService({
serviceName: serviceName,
hasServerMetadata: false,
useJsonp: true,
jsonResultsAdapter: jsonResultsAdapter
});
var manager = new breeze.EntityManager({ dataService: ds });
model.initialize(manager.metadataStore);
Two problems:
Your datacontext method does not return a promise so the caller cannot find anything to hang the then or fail call on.
You should be callingcatch, not fail
1. Return a promise
Your saveSettings method did not return a result in the success case so it must fail. Your method must also return a promise in the fail case ... or it will also fail.
And while I'm here, since there is no real difference between your success and fail case, you might as well move the vm.loading toggle to the finally clause.
Try this instead:
function update() {
vm.loading = true;
return datacontext.saveSettings()
.then(function () {
// .. success handling if you have any
})
.catch(function (data) {
// .. fail handling if you have any
})
.finally(funtion() {
vm.loading = false; // turn it off regardless
});
}
And now the dataContext ... notice the two return statements return a promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
} else {
log("Nothing to save");
// WHY ARE YOU FAILING WHEN THERE IS NOTHING TO SAVE?
// Breeze will handle this gracefully without your help
return breeze.Q.reject(new Error('Nothing to save'));
};
}
2. Use catch
I assume you have configured Breeze to use Angular's $q for promises (you should be using the "breeze.angular" service and have injected "breeze" somewhere).
$q does not have a fail method! The equivalent is catch. For some reason you have both attached to your query. You'll get the ReferenceError exception immediately, before the server has a chance to respond ... although it will launch the request and you will get a callback from the server too.
Try just:
return manager.saveChanges()
.then(saveSucceeded)
.catch(saveFailed);
You see many Breeze examples that call fail and fin. These are "Q.js" methods; "Q.js" is an alternative promise library - one used by Breeze/KnockOut apps and it was the basis for Angular's $q.
Both "Q.js" and $q support the now-standard catch and finally promise methods. We're slowly migrating our example code to this standard. There is a lot of old fail/finally code around in different venues. It will take time.
Sorry for the confusion.
Update savesetting function like below to return Promise.
function saveSettings() {
if (manager.hasChanges()) {
return manager.saveChanges(); // return promise
} else {
log("Nothing to save");
return false;
};
}
Then you can call then and fail in update function like following.
function update() {
vm.loading = true;
return datacontext.saveSettings().then(function () {
vm.loading = false; // never gets called
}).fail(function (data) {
vm.loading = false; // never gets called
});
}

Testing Promises/Async flow with Sinon.js

I am having trouble testing the returned value of a function that waits for a promise to be resolved before executing.
Javascript Method (serviceClient._getProduct returns a jQuery ajax promise object)
serviceClient.getProductName = function(id, storeId) {
$.when(self._getProduct(id)).done(function(data) {
return data.name;
});
};
Test Code
before(function() {
serviceClient.sampleResponse = fixture.load('product_response.json')[0];
$.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});
describe('.getProductName', function() {
it('should return the product name', function() {
var name = serviceClient.getProductName(serviceClient.sampleResponse.id);
$.when($.ajaxStub.returnValue).done(function() {
expect(name).to.equal(serviceClient.sampleResponse.name);
});
});
});
When I step through the call stack it seems to be executing correctly (steps inside of the promise callback in the actual js file, then steps into the test promise callback to assert), however the name variable in the test is still coming back as undefined. Any feedback would be appreciated.
You're trying to return data.name synchronously in serviceClient.getProductName, which can't be done since it depends on an asynchronous ajax request.
You're doing return data.name; inside your callback, which won't get you the expected result: if you'd return something synchronously, that return sentence should be at the scope outside that closure.
To simplify: if there's anything you can return there, it's a Deferred or Promise. It should be something like this:
serviceClient.getProductName = function(id, storeId) {
var deferredName = $.Deferred();
$.when(self._getProduct(id)).done(function(data) {
deferredName.resolve(data.name);
}).fail(function(jqXHR, textStatus, error) {
deferredName.reject(error);
});
return deferredName.promise();
// Or, if needed (I don't think so, it's resolved and rejected here)
// return deferredName;
};
Then, in your test:
before(function() {
serviceClient.sampleResponse = fixture.load('product_response.json')[0];
$.ajaxStub = sinon.stub($, 'ajax').returns(new $.Deferred().resolve(serviceClient.sampleResponse));
});
describe('.getProductName', function() {
it('should return the product name', function() {
serviceClient.getProductName(serviceClient.sampleResponse.id)
.done(function(name) {
expect(name).to.equal(serviceClient.sampleResponse.name);
});
});
});
Leaving code aside, the conceptual mistake is that you can return name synchronously from getProductName, when that function gets the product asyncrhonously (it won't be able to access its name until the deferred is resolved).
Note: you could implement getProductName using then, that returns a Promise (which is a subset of Deferred, but you can usually get away with it and code looks even clearer):
serviceClient.getProductName = function(id, storeId) {
return $.when(self._getProduct(id)).then(function(data) {
return data.name;
});
};
To remove that, also unnecessary $.when(...), you could return a Promise from _getProduct as well (if you've got a deferred, getting a Promise for it is as simple as calling deferred.promise()).

Javascript, AngularJS - Global variable not being correctly assigned

The scope variable $scope.quizArray appears as undefined when accessed from the controller. The block of code below is definitely read as I have previously put tests in.
app.factory('getQuizService', function($http){
return {
getQuiz:function(videoId,scope){
var $promise=$http.post("http://localhost/PHP/getQuiz.php", {'videoId': videoId});
$promise.then(function(msg){
scope.quizArray = "TEST";
});
}
}
});
Controller code - The service is called by:
function getQuizList(){
getQuizService.getQuiz(videoIdService.getId(),$scope);
alert($scope.quizArray);
}
however the alert produces the result 'undefined' instead of 'TEST'
Can anyone see where I've gone wrong? Any help would be appreciated. Thanks
Is there a reason you're doing it like this?
Take a look at the approach(es) below:
Refer to: http://docs.angularjs.org/api/ng/service/$q for more information about how the promises work.
For example, the reason why the approach that lib3d has pointed out works:
then(successCallback, errorCallback, notifyCallback) – regardless of when the promise was or will be resolved or rejected, then calls one of the success or error callbacks asynchronously as soon as the result is available. The callbacks are called with a single argument: the result or rejection reason. Additionally, the notify callback may be called zero or more times to provide a progress indication, before the promise is resolved or rejected.
This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback. It also notifies via the return value of the notifyCallback method. The promise can not be resolved or rejected from the notifyCallback method.
Code:
app.factory('getQuizService', function($http){
return {
getQuiz:function(videoId,scope){
return $http.post("http://localhost/PHP/getQuiz.php", {'videoId': videoId});
//If you want to handle the promise here, you can include the $q service in your controller and do this:
var deferred = $q.defer();
$http.post("...").then(function(data) {
//Special treatment here
deferred.resolve(data)
}, function(error) {
deferred.reject(error)
})
return deferred.promise
//As indicated by lib3d, you can directly do this as well:
return $http.post("...").then(function(data) {
//treatment
return data
}, function(error) {
//error handling
});
}
}).controller("Ctrl", function($scope, getQuizService) {
getQuizService.getQuiz(1).then(function(data) {
$scope.quizArray = data;
//Code when everything goes ok,
}, function(error) {
//Code when everything goes false;
})
})

Categories