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";
});
Related
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.
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
});
}
I am still fairly new to promises and am using bluebird currently, however I have a scenario where I am not quite sure how to best deal with it.
So for example I have a promise chain within an express app like so:
repository.Query(getAccountByIdQuery)
.catch(function(error){
res.status(404).send({ error: "No account found with this Id" });
})
.then(convertDocumentToModel)
.then(verifyOldPassword)
.catch(function(error) {
res.status(406).send({ OldPassword: error });
})
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch(function(error){
console.log(error);
res.status(500).send({ error: "Unable to change password" });
});
So the behaviour I am after is:
Goes to get account by Id
If there is a rejection at this point, bomb out and return an error
If there is no error convert the document returned to a model
Verify the password with the database document
If the passwords dont match then bomb out and return a different error
If there is no error change the passwords
Then return success
If anything else went wrong, return a 500
So currently catches do not seem to stop the chaining, and that makes sense, so I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors, or if there is a better way to structure this to get some form of branching behaviour, as there is a case of if X do Y else Z.
Any help would be great.
This behavior is exactly like a synchronous throw:
try{
throw new Error();
} catch(e){
// handle
}
// this code will run, since you recovered from the error!
That's half of the point of .catch - to be able to recover from errors. It might be desirable to rethrow to signal the state is still an error:
try{
throw new Error();
} catch(e){
// handle
throw e; // or a wrapper over e so we know it wasn't handled
}
// this code will not run
However, this alone won't work in your case since the error be caught by a later handler. The real issue here is that generalized "HANDLE ANYTHING" error handlers are a bad practice in general and are extremely frowned upon in other programming languages and ecosystems. For this reason Bluebird offers typed and predicate catches.
The added advantage is that your business logic does not (and shouldn't) have to be aware of the request/response cycle at all. It is not the query's responsibility to decide which HTTP status and error the client gets and later as your app grows you might want to separate the business logic (how to query your DB and how to process your data) from what you send to the client (what http status code, what text and what response).
Here is how I'd write your code.
First, I'd get .Query to throw a NoSuchAccountError, I'd subclass it from Promise.OperationalError which Bluebird already provides. If you're unsure how to subclass an error let me know.
I'd additionally subclass it for AuthenticationError and then do something like:
function changePassword(queryDataEtc){
return repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword);
}
As you can see - it's very clean and you can read the text like an instruction manual of what happens in the process. It is also separated from the request/response.
Now, I'd call it from the route handler as such:
changePassword(params)
.catch(NoSuchAccountError, function(e){
res.status(404).send({ error: "No account found with this Id" });
}).catch(AuthenticationError, function(e){
res.status(406).send({ OldPassword: error });
}).error(function(e){ // catches any remaining operational errors
res.status(500).send({ error: "Unable to change password" });
}).catch(function(e){
res.status(500).send({ error: "Unknown internal server error" });
});
This way, the logic is all in one place and the decision of how to handle errors to the client is all in one place and they don't clutter eachother.
.catch works like the try-catch statement, which means you only need one catch at the end:
repository.Query(getAccountByIdQuery)
.then(convertDocumentToModel)
.then(verifyOldPassword)
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch(function(error) {
if (/*see if error is not found error*/) {
res.status(404).send({ error: "No account found with this Id" });
} else if (/*see if error is verification error*/) {
res.status(406).send({ OldPassword: error });
} else {
console.log(error);
res.status(500).send({ error: "Unable to change password" });
}
});
I am wondering if there is a way for me to somehow force the chain to stop at a certain point based upon the errors
No. You cannot really "end" a chain, unless you throw an exception that bubbles until its end. See Benjamin Gruenbaum's answer for how to do that.
A derivation of his pattern would be not to distinguish error types, but use errors that have statusCode and body fields which can be sent from a single, generic .catch handler. Depending on your application structure, his solution might be cleaner though.
or if there is a better way to structure this to get some form of branching behaviour
Yes, you can do branching with promises. However, this means to leave the chain and "go back" to nesting - just like you'd do in an nested if-else or try-catch statement:
repository.Query(getAccountByIdQuery)
.then(function(account) {
return convertDocumentToModel(account)
.then(verifyOldPassword)
.then(function(verification) {
return changePassword(verification)
.then(function() {
res.status(200).send();
})
}, function(verificationError) {
res.status(406).send({ OldPassword: error });
})
}, function(accountError){
res.status(404).send({ error: "No account found with this Id" });
})
.catch(function(error){
console.log(error);
res.status(500).send({ error: "Unable to change password" });
});
I have been doing this way:
You leave your catch in the end. And just throw an error when it happens midway your chain.
repository.Query(getAccountByIdQuery)
.then((resultOfQuery) => convertDocumentToModel(resultOfQuery)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
.then((model) => verifyOldPassword(model)) //inside convertDocumentToModel() you check for empty and then throw new Error('no_account')
.then(changePassword)
.then(function(){
res.status(200).send();
})
.catch((error) => {
if (error.name === 'no_account'){
res.status(404).send({ error: "No account found with this Id" });
} else if (error.name === 'wrong_old_password'){
res.status(406).send({ OldPassword: error });
} else {
res.status(500).send({ error: "Unable to change password" });
}
});
Your other functions would probably look something like this:
function convertDocumentToModel(resultOfQuery) {
if (!resultOfQuery){
throw new Error('no_account');
} else {
return new Promise(function(resolve) {
//do stuff then resolve
resolve(model);
}
}
Probably a little late to the party, but it is possible to nest .catch as shown here:
Mozilla Developer Network - Using Promises
Edit: I submitted this because it provides the asked functionality in general. However it doesn't in this particular case. Because as explained in detail by others already, .catch is supposed to recover the error. You can't, for example, send a response to the client in multiple .catch callbacks because a .catch with no explicit return resolves it with undefined in that case, causing proceeding .then to trigger even though your chain is not really resolved, potentially causing a following .catch to trigger and sending another response to the client, causing an error and likely throwing an UnhandledPromiseRejection your way. I hope this convoluted sentence made some sense to you.
Instead of .then().catch()... you can do .then(resolveFunc, rejectFunc). This promise chain would be better if you handled things along the way. Here is how I would rewrite it:
repository.Query(getAccountByIdQuery)
.then(
convertDocumentToModel,
() => {
res.status(404).send({ error: "No account found with this Id" });
return Promise.reject(null)
}
)
.then(
verifyOldPassword,
() => Promise.reject(null)
)
.then(
changePassword,
(error) => {
if (error != null) {
res.status(406).send({ OldPassword: error });
}
return Promise.Promise.reject(null);
}
)
.then(
_ => res.status(200).send(),
error => {
if (error != null) {
console.error(error);
res.status(500).send({ error: "Unable to change password" });
}
}
);
Note: The if (error != null) is a bit of a hack to interact with the most recent error.
I think Benjamin Gruenbaum's answer above is the best solution for a complex logic sequence, but here is my alternative for simpler situations. I just use an errorEncountered flag along with return Promise.reject() to skip any subsequent then or catch statements. So it would look like this:
let errorEncountered = false;
someCall({
/* do stuff */
})
.catch({
/* handle error from someCall*/
errorEncountered = true;
return Promise.reject();
})
.then({
/* do other stuff */
/* this is skipped if the preceding catch was triggered, due to Promise.reject */
})
.catch({
if (errorEncountered) {
return;
}
/* handle error from preceding then, if it was executed */
/* if the preceding catch was executed, this is skipped due to the errorEncountered flag */
});
If you have more than two then/catch pairs, you should probably use Benjamin Gruenbaum's solution. But this works for a simple set-up.
Note that the final catch only has return; rather than return Promise.reject();, because there's no subsequent then that we need to skip, and it would count as an unhandled Promise rejection, which Node doesn't like. As is written above, the final catch will return a peacefully resolved Promise.
I wanted to preserve the branching behaviour that Bergi's answer had, yet still provide the clean code structure of unnested .then()'s
If you can handle some ugliness in the machinery that makes this code work, the result is a clean code structure similar to non-nested chained .then()'s
One nice part of structuring a chain like this, is that you can handle all the potential results in one place by chainRequests(...).then(handleAllPotentialResults) this might be nice if you need to hide the request chain behind some standardised interface.
const log = console.log;
const chainRequest = (stepFunction, step) => (response) => {
if (response.status === 200) {
return stepFunction(response, step);
}
else {
log(`Failure at step: ${step}`);
return response;
}
};
const chainRequests = (initialRequest, ...steps) => {
const recurs = (step) => (response) => {
const incStep = step + 1;
const nextStep = steps.shift();
return nextStep ? nextStep(response, step).then(chainRequest(recurs(incStep), incStep)) : response;
};
return initialRequest().then(recurs(0));
};
// Usage
async function workingExample() {
return await chainRequests(
() => fetch('https://jsonplaceholder.typicode.com/users'),
(resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/'); },
(resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/3'); }
);
}
async function failureExample() {
return await chainRequests(
() => fetch('https://jsonplaceholder.typicode.com/users'),
(resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/fail'); },
(resp, step) => { log(`step: ${step}`, resp); return fetch('https://jsonplaceholder.typicode.com/posts/3'); }
);
}
console.log(await workingExample());
console.log(await failureExample());
The idea is there, but the interface exposed could probably use some tweaking.
Seeing as this implementation used curried arrow functions, the above could potentially be implemented with more direct async/await code
Quick question, if I do this:
setInterval(function() {
try {
riskyFunc();
} catch(e){
console.log(e);
}
}, 1000);
In my head I am thinking that if anything goes wrong in riskyFunc(), it will be caught. Is this true? There are also some async calls I know of for sure inside riskyFunc().
Yes, it will be caught: but only when the callback is executed. That is, if riskyFunc throws an exception, it won't be caught in your example until the callback executes in one second.
You've probably heard before that you have to be careful with exceptions when using asynchronous methods, and the usual mistake people make is this:
try {
setInterval(function() {
riskyFunc();
}, 1000);
} catch(e) {
console.error(e);
}
They are confused when riskyFunc throws an exception and it isn't caught. It isn't caught because the exception doesn't happen when you call setInterval; it happens when setInterval invokes the anonymous function sometime in the future, which is outside of the context of the original try/catch block. You are doing it the correct way: by doing the exception handling inside the callback.
If riskyFunc in turn invokes asynchronous calls, those too have to handle exceptions in this manner. For example:
function riskyFunc() {
// do some stuff
asyncFn(function(){
throw new Error('argh');
}
}
That exception will not get caught in the try/catch block inside your setInterval call. You'll have to keep applying the pattern on down:
function riskyFunc() {
// do some stuff
asyncFn(function() {
try {
// work that may throw exception
} catch(e) {
console.error(e);
}
}
}
If you want the exception to "propagate up", you'll have to use promises, or some other way to indicate success/failure. Here's a common method, by using a "done" callback that is capable of reporting an error:
function riskyFunc(done) {
// do some stuff
asyncFn(function() {
try {
// do some more risky work
done(null, 'all done!');
} catch(e) {
done(e);
}
}
}
Then you can call that in your setTimeout and take into account possible asynchronous failures:
setTimeout(function() {
try {
riskyFunc(function(err, msg) {
// this will cover any asynchronous errors generated by
// riskyFunc
if(err) return console.error(err);
console.log(msg);
});
} catch(e) {
// riskyFunc threw an exception (not something it
// invoked asynchronously)
console.error(e);
}
}
setInterval already puts the block in an asynchronous block. And exceptions can't be caught in out-of-sync. The right pattern is to use a Promise object, and call a fail() operation if something goes wrong. For this example, I'm using jQuery's Deferred object, but any promise library has similar usage:
var promise = $.Deferred();
setInterval(function () {
try{
riskyFunc();
} catch (e) {
promise.reject(e);
}
promise.resolve(/* some data */);
}, 1000);
promise
.done(function (data) { /* Handled resolve data */ })
.fail(function (error) { /* Handle error */ });
Note that since you're using setInterval instead of setTimeout that this will be called every second unless you clear the timeout, so if you need to call the function multiple times in parallel, you might want an array of promises.
If riskyFunc is
function() {
process.nextTick(function() {
throw "mistake";
});
}
your catch block will not catch. I believe this is the case you are worried about, and all you can do is set global exception handlers, or hope for the best. (No, promises will not catch this. They are not magic.)
Thanks #Ethan Brown for the detailed explanation. I think your last setTimeout is missing the timer - see below
setTimeout(function() {
try {
riskyFunc(function(err, msg) {
// this will cover any asynchronous errors generated by
// riskyFunc
if(err) return console.error(err);
console.log(msg);
});
} catch(e) {
// riskyFunc threw an exception (not something it
// invoked asynchronously)
console.error(e);
}
}, 1000)
I am wondering how to chain errors on Q using finally.
Consider following code
function p1() {
throw new Error("p1 error");
}
function p2() {
throw new Error("p2 error");
}
function p3() {
return Q.fcall(p1)
.finally(function () {
return Q.fcall(p2);
});
}
p3()
.done();
The Error with message "p1 error" is lost since it was override by Error "p2 error". How can I throw all the errors (or combine the errors)?
Currently, I am working on a socket connection on nodejs. I am using .finally() to close the socket after each connection.
However, errors (eg: authentication error) before .finally() will be overriden by one in .finally() (eg: connection close error). So, I am wondering how to get all errors
Thanks
You could do something like this:
function p3() {
return Q.fcall(p1)
.finally(function(p) {
return Q.fcall(p2).catch(function(e) {
if (!p.isRejected()) throw e;
var agg = new Error("multiple errors occured");
agg[0] = p.inspect().reason;
agg[1] = e;
agg.length = 2;
throw agg;
});
});
}
(based on Bluebird's AggregateError)
Of course it is tedious, but I can't imagine something better.