Please see the demo here
function get(url) {
return $http.get(url)
.then(function(d){
return d.data
},
function(err){ //will work without handling error here, but I need to do some processing here
//call gets here
//do some processing
return err
})
}
get('http://ip.jsontest.co')
.then(function(response){
$scope.response = "SUCCESS --" + JSON.stringify(response);
}, function(err){
$scope.response = "ERROR -- " + err;
})
I have a library function, get, which returns a promise. I am processing the error there, and returns it (where I commented //do some processing ). I was expecting in the client, it calls the error/fail handler. instead it prints "SUCCESS --" + error
I can make this work with $q and reject, but is there a way without?
Generally:
Whenever you return from a promise handler, you are resolving indicating normal flow continuation.
Whenever you throw at a promise handler, you are rejecting indication exceptional flow.
In a synchronous scenario your code is:
function get(url){
try{
return $http.get(url);
} catch(err){
//handle err
}
}
If you want to pass it further, you need to rethrow:
function get(url){
try{
return $http.get(url);
} catch(err){
//handle err
throw err;
}
}
Promises are exactly like that:
function get(url){
return $http.get(url)
.then(function(d){
return d.data
},
function(err){ //will work without handling error here
//call gets here
//do some processing
throw err; // note the throw
})
};
Or with even niftier syntax:
function get(url){
return $http.get(url).catch(function(err){
// do some processing
throw err;
});
}
Replace return err with $q.reject(err), you need to inject $q of course.
In promise chaining, if you want to pass the error down, you'll need to return a rejected promise from current error handler. Otherwise, if the return value is an immediate value or resolved promise, the error is considered to be handled, so later error handlers down the chain won't be called.
Related
I'm faced with a small issue when trying to chain complex function calls with Promises and callbacks.
I have a main function, which calls subroutines. In these routines API calls are made.
For Example:
function handle(){
new Promise(function(resolve, reject){
let result = doAPICall1()
if (result === true) resolve(true);
reject(JSON.stringify(result))
}).then(function(){
let result = doAPICall2()
if (result === true) return true
throw new Error(JSON.stringify(result))
}).catch(error){
console.error(JSON.stringify(error))
}
}
function doAPICall1(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
function doAPICall2(){
axios.get('...').then(function(){
return true
}).catch(function(error){
return error
})
}
But when I execute this example, doAPICall2 will be executed, while doAPICall1 is still running.
It only occures when long running calls are made.
Does anyone can give me a hint? Thank you!
You're overdoing manually a lot of things that Promises already do for you:
axios.get already returns a Promise, so there is no point in return a the response when it resolves and return a false when it rejects. A catch handler at the end of a Promise chain already handles all errors that may arise during the chain, so you don't need to catch every Promise.
I would do something like:
function doAPICall1(){
return axios.get('...');
}
function doAPICall2(){
return axios.get('...');
}
function handle(){
// in case you would use the api calls results.
let firstResult = null;
let secondResult = null;
return doAPICall1()
.then(res => {firstResult = res})
.then(() => doAPICall2())
.then(res => {
secondResult = res;
return []
})
}
I guess you will use the Api calls results for something. With the code above, you could consume the handle() function like follows:
function someSortOfController(){
handle().then(results => {
console.log(results[0]); // first api call result
console.log(results[1]); // second api call result
})
.catch(err => {
// here you will find any error, either it fires from the first api call or from the second.
// there is *almomst* no point on catch before
console.log(err);
})
}
There, you will access any error, either it came from the first api call or the second. (And, due to how Promises work, if the first call fails, the second won't fire).
For more fine grained error control, you may want to catch after every Promise so you can add some extra logs, like:
function doAPICall1(){
return axios.get('...')
.catch(err => {
console.log('the error came from the first call');
throw err;
});
}
function doAPICall2(){
return axios.get('...')
.catch(err => {
console.log('the error came from the second call');
throw err;
});
}
Now, if the first api call fails, everything will work as before (since you're throwing the error again in the catch), but you have more control over error handling (maybe the error returning from API calls is not clear at all and you want this kind of control mechanism).
Disclaimer
This answer doesn't answer why your code acts like it does. However, there are so much things wrong in your code, so I think providing you with an example about using Promises is more valuable.
Don't worry and take some time to understand Promises better. In the example code below, doAPICall function return a Promise which resolves to a value, not the value itself.
function handle() {
doAPICall().then(result => {
//do something with the result
}).catch(error => {
//catch failed API call
console.error(error)
})
}
doAPICall() {
// this returns a Promise
return axios.get(...)
}
As a node programmer. I'm used to use "nodebacks" for handling errors in my code:
myFn(param, function(err, data) {
if (err){
//error handling logic
}
else {
// business logic
}
});
When writing that function, I can do something like:
var myFn = function(param, callback){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
callback(new Error("error with calculation"), null);
}
...
someAsyncOp(calcN,function(err, finalResult){
if(err) return callback(err, null);
callback(null, finalResult); // the error is null to signal no error
});
};
How would I do this sort of error handling with promises?
Rule of Thumb
Whenever you have a doubt about how to do something with promises - think about the synchronous version.
try{
var result = myFn(param);
// business logic with result
} catch(e) {
//error handling logic
}
This, at least to me looks a lot cleaner than a callback with a first parameter that is sometimes null.
The promises way is almost always very similar to the synchronous version of the problem:
myFn(param).then(function(result){
// business logic with result
}).catch(function(e){
//error handling logic
});
Where myFn would look something like when working with callbacks:
var myFn = function(param){
return new Promise(function(resolve, reject){
var calc = doSomeCalculation(param);
if(calc === null) { // or some other way to detect error
reject(new Error("error with calculation"), null);
}
someAsyncOp(calcN,function(err, finalResult){
if(err) reject(err);
resolve(finalResult);
})
});
};
Working with callbacks/nodebacks
This is only something you should have to do when working with callbacks, when working with promises it is a lot simpler, and you can do:
var myFn = function(param){
var calc = doSomeCalculation(param);
...
return someAsyncOp(calcN); // returning a promise.
}
Moreover, when working inside promise chains, you get throw safety:
myFn(param).then(function(calcN){
// here, you throw to raise an error and return to resolve
// new Promise should be used only when starting a chain.
}).catch(function(err){
// handle error
}).then(function(){
// ready to go again, we're out of the catch
});
Note, some libraries like Bluebird , RSVP and Q offer syntactic sugar and automatic promisification of methods so you rarely have to use new Promise yourself.
Also, consider reading this and that to learn more about promise error handling.
If you're using the async/await syntax, you can just use the regular try-catch syntax for error handling.
// your promise function
const myFn = function(param){
return new Promise(function(resolve, reject){
if (someLogic()) {
resolve(someValue);
} else {
reject('failure reason');
}
});
}
// Define the parent function as an async function
async function outerFn(param) {
try {
// Wait for the promise to complete using await
const result = await myFn(param)
// business logic with result
} catch (e) {
//error handling logic
}
}
I've got a somewhat complicated flow that receives an entryUrl from a database, checks where it redirects to, and then updates it with an exitUrl.
Basically the flow should be like so:
retrieve an Url without an exit url
get the Url.entryUrl's headers using request
if there's an unexpected response or connection was reset, flag this Url and continue with the next one
parse the exitUrl resulting from the request performed
store the exitUrl
continue with the next Url
if no Url available, try again after 5 seconds
if any unexpected error in the above chain or subchains, try again after 60 seconds
My current implementation is like so, using the Bluebird javascript promise style:
function processNext() {
return api.getUrlWithoutExitUrl()
.then(function followEntryUrl(url)
{
if (!url || !url.entryUrl)
{
throw new NoUrlAvailableError();
}
log.info('getting exit url for ' + url.entryUrl);
return [
request({
method : 'HEAD',
url : url.entryUrl,
followAllRedirects : true,
maxRedirects : 20
})
.catch(ResponseError, function()
{
log.error('got strange response');
})
.catch(ConnResetError, function()
{
log.error('connection was reset');
})
.then(function removeInvalidUrl()
{
log.info('remove invalid url'); //FIXME: after doing this, we should not continue with the other `then` calls
}),
url
];
})
.spread(function parseExitUrl(res, url)
{
if (!res[0] || !res[0].request || !res[0].request.uri || !res[0].request.uri.href)
{
throw new InvalidUrlError();
}
return [res[0].request.uri, url];
})
.spread(function storeExitUrl(parsedExitUrl, url)
{
return api.setUrlExitUrl(url, parsedExitUrl);
})
.then(processNext)
.catch(InvalidUrlError, function()
{
log.info('an attempted url is invalid, should set as processed and continue with next immediately');
})
.then(processNext)
.catch(NoUrlAvailableError, function()
{
log.info('no url available, try again after a while');
})
.delay(5000)
.then(processNext)
.catch(function(err)
{
log.error('unexpected error, try again after a long while');
log.error(err);
log.error(err.constructor);
})
.delay(60000)
.then(processNext);
}
processNext();
function ResponseError(e)
{
return e && e.code === 'HPE_INVALID_CONSTANT';
}
function ConnResetError(e)
{
return e && e.errno === 'ECONNRESET';
}
Now, the problem is that if there's a ConnResetError or a ResponseError, the catch blocks are executed as they should be, but the then blocks following the spread call are also executed -- yet I want execution to stop after having done something after catching these 2 specific error types.
How would I achieve such flow of execution?
Just like in synchronous code - if you have a catch in which you want to perform some processing and then propagate the error - you can rethrow it:
Synchronous code:
try {
a = makeRequest();
} catch(e) {
// handle
throw e;
}
With promises:
makeRequest().catch(e => {
// handle
throw e; // also a good idea to add data to the error here
});
From your inner promise, when you first catch the ResponseError or ConnResetError, you return normally (i.e. not throwing), so the subsequent promise chain succeeds, executing its then() and spread() branches, rather than failing and going to the catch() branches.
You probably want to rewrite your inner promise catch blocks like so:
...
.catch(ResponseError, function(err) {
log.error('got strange response');
throw err;
})
...
Basically, re-throw the Error you have caught if you want to continue treating it as an error.
With the code below, my controller's publish() would always go to createCompleted() even if the server returned 500. I was under impression that catch() would be executed when 400 or 500 codes are returned from the server.
// in service
function create(item) {
return $http
.post(api, item)
.then(createCompleted)
.catch(createFailed);
function createCompleted(response) {
return response.data;
}
function createFailed(error) {
$log.error('XHR Failed for create: ' + error.data);
}
}
// in controller
function publish(item) {
item.published = true;
return itemService.create(item)
.then(createCompleted)
.catch(createFailed);
function createCompleted(response) {
alertService.add('success', 'success.');
$state.go('^');
}
function createFailed(error) {
alertService.add('error', 'failed');
}
}
While the controller's createFailed() doesn't hit, the service createFailed() always hits.
What is going on here?
Well that is because you are not propagating the error properly. you would need to throw an exception or reject explicitly from createFailed function.
function createFailed(error) {
$log.error('XHR Failed for create: ' + error.data);
throw error;
// or
return $q.reject(error); // inject $q
}
So in your case since you are not returning anything it is assumed to be resolved from the returned promise with the value "undefined".
I'm trying to learn using deferred and I'm stumbled as I'm not getting expected arguments in the "then" block.
var makeCall = function (err, param) {
var deferred = Q.defer();
setTimeout(function() {
console.log(1111, err, param);
deferred.resolve(err, param);
}, 1000);
return deferred.promise;
};
makeCall('test', '11').then(function(err, data) {
console.log(222, err, data);
});
Console. with 1111 outputs correct data that was returned from an Ajax call but 222 does not.
http://jsfiddle.net/M2V44/
deferred.resolve can accept only one argument and that is to mark the success of the asynchronous call. To notify of the failure, you need to use deferred.reject. So your code has to be changed like this
var makeCall = function(err,param){
setTimeout(function () {
console.log(1111, err, param);
var deferred = Q.defer();
if (err) {
deferred.reject(err);
} else {
deferred.resolve(param);
}
}, 1000);
return deferred.promise;
};
makeCall(undefined, '11').then(function (data) {
console.log(222, data);
}, function (err) {
console.log(333, err);
});
This will print 222 '11', to simulate the failure case, just invoke makeCall with any Truthy value as the first argument, for example
makeCall('11')....
it will invoke the failure handler, and the output will be 333 '11'.
In your case, I'd avoid the deferred altogether.
var makeCall = function(err,param){
if(err) return Q.reject(err);
return Q(param).delay(1000);
};
(fiddle)
The usage is similar to thefoureye's answer from earlier, since promises are like synchronous code, you interact with them using return values and catch statements. Nodebacks ((err,data)) and callbacks more generally remove many desirable properties from asynchronous code, and promises aim to restore those properties.
makeCall(new Error("Hello"),"SomeValue").then(function(cata){
console.log("Got correct data!",data);
}).catch(function(err){
console.log("Got error :(",err); // this would happen since we passed an error.
});
I also assume that that this function is imaginary, and not representative of a real API.
You mainly use deferred objects when converting an API to promises, which you don't need to in this case.
Note, a) Not certain if interpret Question correctly; b) Promise appear not implemented universally same in every browser, nor at jsfiddle. This may be helpful JavaScript Promises http://www.html5rocks.com/en/tutorials/es6/promises/ (where below piece can be tried at console; should also work at mozilla nightly console, which appear to implement Promise object)
Try this (pattern)
var makeCall = function(err, param) {
return new Promise(function(resolve, reject ) {
setTimeout(function() {
console.log(1111, err, param);
return (err && param) ?
resolve(err, param) :
reject(Error("error"))
})
}, 1000);
};
makeCall("test", "11")
.then(function(result) {
console.log(222, result);
makeCall("test2","11");
makeCall("abc", 123) // `chain` test
},
function(err) {
console.log(err)
});