I'm new to AngularJS and I would like to know why does AngularJS keeps going on the next chaining operation even if one of my previous chaining functions fails.
// Manipulate data
function manipulationData(data) {
return data.total + 2;
}
/*
* Begin chaining promises
*/
var deferred = $q.defer();
// JSON Request
$http.get('/json_test.json')
// If results is ok, then manipulate some data
.then(function(results) {
if(results.status == "ok") {
return manipulationData(results);
} else {
deferred.reject("Some data error");
}
}, function(reason) {
deferred.reject("Error request: " + reason);
})
// If manipulation is success
.then(function(results) {
if(results > 5) {
return $http.get('http://host.com/second');
} else {
deferred.reject("Error! Data is invalid");
}
}, function(reason) {
deferred.reject("Error request: " + reason);
})
.then(function(result){
return $http.get('http://host.com/second');
})
return deferred.promise;
For some reason, the application keeps executing all the function even if one of them failed. I want the operation to stop when the 1st promise is not working.
For example, if second operation failed, it should throw the error "Some data error".
Thank you
It seems to me that you face a problem only when your error condition fires (i.e. results.status is not "ok"), because you don't return anything in this case. When you don't return anything Q will fallback to the current promise (returned by the last function) and as it succeeded the next method will also be called (as the promise it is hooked to is fulfilled). Here is an example how to handle custom errors with Q:
promise.then(function(data) {
if (p(data)) {
return doSomethingAsync(data);
} else {
return $q.reject("Some error...")
}
}).then(function(data) {
doSomething(data); // This will be called only if p(data) was true.
}).catch(function(err) {
console.log(err); // This will be called only if p(data) was false.
})
$q is a normal AngularJS service, so you just need to add it as a parameter. You may also refer to the docs page - https://code.angularjs.org/1.2.12/docs/api/ng.$http
Just from a brief look at your code, there doesn't seem to be any reason to use $q at all (well it will be used indirectly ofc.
// Manipulate data
function manipulationData(data) {
return data.total + 2;
}
// JSON Request
return $http.get('/json_test.json')
// If results is ok, then manipulate some data
.then(function(results) {
if(results.status == "ok") {
return manipulationData(results);
} else {
throw new Error("Some data error");
}
}, function(reason) {
throw new Error("Error request: " + reason);
})
// If manipulation is success
.then(function(results) {
if(results > 5) {
return $http.get('http://host.com/second');
} else {
throw new Error("Error! Data is invalid");
}
}, function(reason) {
//Note: This will give 'Error request: Error request: {reason}'
// in cases where '/json_test.json' failed
throw new Error("Error request: " + reason);
})
.then(function(result){
return $http.get('http://host.com/second');
});
As promises works more like try - catch blocks than one may think, when you handle the error, but don't throw another in the handler, your actually saying you have recovered (Which was not your intention)...
Here is a plunk where you can see that effect if you remove the first block of code that is commented out.
http://plnkr.co/edit/kegH8Ca3O3EjQqGCi143?p=preview
angular.forEach(self.stages, function(stage) {
promise = promise.then(function() {
stage.state = 'progress';
return $timeout(function() {
stage.state = 'done';
if(stage.id == 3) throw Error("Test");
}, 1000, true);
}
// add this code to see that we recover.
//, function(error) {
// stage.state = 'error';
//}
// add this code to see how we re-throw the error
//, function(error) {
// stage.state = 'error';
// throw error;
//}
);
});
Related
My Angular v1.x directive calls a function when a button is clicked and depending on the input I set some variables:
$scope.offerRespond = function() {
if (offer.i.offerResponse == 1) {
// set some variables
}
else if (offer.i.offerResponse == 0) {
$http({method: 'GET', url: frontbaseurl+'/offer_types/' + id + '.json'})
.then(function successCallback(response) {
$scope.reason = response.data.data.name;
}, function errorCallback() {
$scope.reason = "Unknown";
}
);
$http.post(frontbaseurl+'/purchases.json', JSON.stringify(dataObj))
.then(function(response){
// continue....
}
}
As can be seen, I make a GET request if the offerResponse == 0. This seems risky because the code continues to execute without before a response from the request is received. I know the approach is to use a 'promise' but how does that apply in this case if the GET request may not be made?
The problem as I see it is if I add a promise and the offerResponse == 1 the promise can't be fulfilled since the GET request would not have been made.
Any suggestions please?
I've had to do something similar in our angular 1.x app, using Typescript, like this:
public myFunc = async (id): Promise<void> => {
return new Promise<void>(async (resolve, reject) => {
if (this.offer.i.offerResponse === 1) {
// set some variables
resolve();
}
else if (this.offer.i.offerResponse === 0) {
try {
const response = await services.getOfferTypes(id);
this.reason = response.data.data.name;
resolve();
} catch (errorResponse) {
this.reason = "Unknown";
reject();
}
}
});
}
Notice I "encapsulated" your http request in a service function. I like separating api calls into their own service, to keep things clean and easy to read. This code is untested, but if I remember correctly it should work something like this.
I am using promise in angular for my web app like this-
var deferred = $q.defer();
On Success -
deferred.resolve(profile); // profile = JSON object
On Failure -
deferred.reject(1); // 1 or no value returned
At end of function -
return deferred.promise;
Then I am handing for this returned Promise Object to call another method.But it doesn't call. While if i use Callback(error,success) it works fine.Can somebody suggest what is wrong with my promise.
Code Snippet-
function open() { // for initializing DB,getting called from service
var deferred = $q.defer();
var options = {
Encryption: {
encryptKey: false, // optional encrypt primary key
secrets: [{
name: 'dddd',
key: 'xxxxxxxxxx'
}]
}
};
var schema = {
stores:[{
name:'profile',
encrypted: true
}]
};
var db = new ydn.db.Storage('nowconferdb', schema, options);
db.onReady(function() {
console.log('DB is initialized'); // getting this
profilestorage.setDB(db); // getting called and setting DB in profilestorage service
deferred.resolve(true);
});
db.addEventListener('fail', function (event) {
var err = event.getError();
if (err.name == 'versionchange') {
console.log('The application is updated, please refresh to upgrade.');
profilestorage.setup(db);
} else {
console.log('connection failed with ' + err.name + ' by ' + err.message);
db = null; // no operation can be placed to the database instance
}
deferred.reject(false);
});
return deferred.promise;
}
This is my calling method -
storageservice.open().then(function() {
console.log('post initializing storageservice'); // not getting it.
});
Thanks a lot for your sincere efforts.
What you should do is to call your function inside the success callback of the promise. Assumming you assign your promise to deferred variable:
deferred.then(
function (data) {
// Here you call your other function/method
// It will be called when 'deferred' promise is resolved
anotherFunction();
},
function (error) {
// Handle the error
}
);
I hope this helps, even though the information you give is not enough.
For put request:
router.put('/:id', controller.update);
My update method look like this:
exports.update = function(req, res) {
if(req.body._id) { delete req.body._id; }
Thing.findById(req.params.id, function (err, thing) {
if (err) { return handleError(res, err); }
if(!thing) { return res.status(404).send('Not Found'); }
var updated = _.merge(thing, req.body);
updated.save(function (err) {
if (err) { return handleError(res, err); }
return res.status(200).json(thing);
});
});
};
Making request:
$http.put('/api/things/'+ thing._id, updatedThingObject)
.success(function(update){
console.log("update", update)
})
.error(function(err){
console.log("err", err)
})
It gives connection error on passing the object while making the request in angular.
The error looks like this:
PUT http://localhost:9000/api/things/56c8325b9a0ee7d00d266495
net::ERR_CONNECTION_REFUSED(anonymous function) # angular.js:11442sendReq #
If I take off the updated object, it makes the request just fine but ofcourse nothing gets updated in
that case. What might be wrong here,please?
I figured.
The reason for the functions not being called is that I have a function that is being called repetitively in Node .
var autoCreate = function(){
console.log("THING CREATED AUTOMATICALLY")
var randomNumb=0;
clearTimeout(randomNumb);
randomNumb = (Math.random()* (10-5) + 5).toFixed(0);
console.log("random number", randomNumb)
var randomThing =randomstring({
length: randomNumb,
numeric: false,
letters: true,
special: false
});
console.log("ranfom thing", randomThing)
Thing.create({
name: randomThing,
readByUser: false
}, function(err, thing) {
console.log("THING IS", thing)
//setTimeout(autoCreate, randomNumb * 1000);
});
}
setTimeout(autoCreate, 10*1000);
Since this is running when post/put request is made, I get connection error. How do I handle this to be able to have this function running and be able to make put/post requests as well?
I want to implement a login function using AngularJS and my backend is in Rails. i decided to implement it using the $httpBackend but I have a problem.
When it gets into the $httpBackend function, it does update the token with the latest token from the database but i need to return the value to my services of which that doesnt seem to be happening. I know this has to do with promise and deferred etc but i am not well conversant with those.
SO this is my code
var authorized = false;
var token;
$httpBackend.whenPOST('https://login').respond(function(method, url, data) {
var loginDetails = data;
var d= $q.defer();
function startToken(loginDetails) {
getTokens.newToken(loginDetails).then(function(result) {
if(result.length > 0) {
var updateDB = "UPDATE preferences SET value='"+result[0].token+"' WHERE description='token'";
$cordovaSQLite.execute(db, updateDB).then(function(res) {
var updateDB1 = "UPDATE preferences SET value='true' WHERE description='logged_in'";
$cordovaSQLite.execute(db, updateDB1).then(function(res) {
var query = "SELECT description, value FROM preferences";
$cordovaSQLite.execute(db, query).then(function(res) {
if(res.rows.length > 0) {
if(res.rows.item(3).value!=null || res.rows.item(3).value!='') {
getTokens.getCRMToken(res.rows.item(2).value).then(function(resulttoken){
if(resulttoken[0].token == res.rows.item(3).value) {
token = res.rows.item(3).value;
}
d.resolve(token)
});
}
} else {
console.log("No results found");
}
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}, function (err) {
console.error(err);
});
}
else {
console.log("reject")
d.reject(result);
}
}, 1000);
return d.promise;
}
var a = startToken(loginDetails).then(function(token) {
// in here the value for token is correct i then go ahead to set the value for authorized and resolve it
console.log(token)
if(token.length > 0){
console.log("authorized true")
authorized = true;
d.resolve(token, authorized)
}
else
{
console.log("authorized false")
authorized = false;
d.reject(token, authorized)
}
return d.promise;
})
// this is where i have my issue. all i want to do is to just check if the value for authorized is true, if yes, return the value for token.
//authorized = true;
//return [200 , { authorizationToken: token }];
});
Complete rewrite
Sadly, I think the short answer is that you cannot use promises with $httpBackend. See this discussion: https://github.com/angular/angular.js/issues/11245
In my original answer, I didn't recognize that you were using the $httpBackend mock as I merely concentrated on your incorrect promise code. The information on promises (https://docs.angularjs.org/api/ng/service/$q) is still valid.
The unit test version of ngMock does not handle promises. If you are using a version of $httpBackend which can handle promises, you need to return the promise, not the [200, "status"].
However, that said, in your rewrite, you also reuse the same promise after it has been resolved which is incorrect. You can only resolve or reject a defer once. So you need to either chain your then() functions or create a new defer. Also, you didn't actually need to create a defer since the newToken() function actually returns a promise.
I have a "cancellable" angularJs $http call like this:
var defer = $q.defer()
$http.post("http://example.com/someUrl", {some: "data"}, {timeout: defer.promise})
And I cancel that ajax request using defer.resolve() because some logic requires it.
Some where else in my code, I have an iterceptor like this:
angular.module("services.interceptor", arguments).config(function($httpProvider) {
$httpProvider.interceptors.push(function($q) {
return {
responseError: function(rejection) {
if(rejection.status == 0) {
alert("check your connection");
}
return $q.reject(rejection);
}
};
});
});
});
Problem:
If there is an Internet connection problem, ajax fails with status 0 and interceptor catches it.
If ajax is cancelled by the timeout promise, than status is also 0 and interceptor catches it.
I can't find out if it is cancelled or got error in responseError handler.
My naive approach is to check if timeout is defined like this:
responseError: function(rejection) {
if(rejection.status == 0 && !rejection.config.timeout) {
alert("check your connection");
}
return $q.reject(rejection);
}
It only guarantees that, there is a timeout condition on that request, not it failed because of it. But it is better than nothing.
Are there a really working way of determining if ajax is failed or cancelled?
I'm using AngularJs 1.1.5
I ended up doing this using a flag, which is checked within the error handler.
I started off following the advice at the end of this AngularJS Github issue: https://github.com/angular/angular.js/issues/1159#issuecomment-25735438
This led me to build a service which used a promise to manually "timeout" the http call - as you have done below - by copying the linked plunk from that issue: http://plnkr.co/edit/P8wKns51GVqw5mwS5l5R?p=preview
But this wasn't quite complete, as this didn't address the issue you raised; in the error handling, how to determine if the call was a genuine server outage / timeout, or simply a manually triggered timeout. In the end, I had to resort to storing it in another variable (similar to the timeout promise), which can be seen in this plunk: http://plnkr.co/edit/BW6Zwu
The main method of this code looks as follows
return function (url) {
var cancelQuery = null;
var queryCancelled = false;
return function runQuery(query) {
if (cancelQuery) {
queryCancelled = true;
cancelQuery.resolve();
}
cancelQuery = $q.defer();
return $http.
get(url, { params: { query: query }, timeout: cancelQuery.promise }).
then(function (response) {
cancelQuery = null;
return response.data;
}, function (error) {
if(queryCancelled) {
console.log("Request cancelled");
queryCancelled = false;
} else {
console.log("Actual Error !");
}
});
};
};
Not too elegant, but it seems to work and avoids any nasty race conditions from what I have observed.
If you abort request using timeout, the timeout is listed in config of response.
Check for .config.timeout in your response object:
function config($httpProvider) {
$httpProvider.interceptors.push(function ($q, $injector) {
return {
request: function (config) {
return config;
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
switch (response.status) {
case -1:
case 500 :
case 501 :
case 502 :
case 503 :
case 504 :
// the webapp sometimes aborts network requests. Those that dropped should not be restarted
if (response.status === -1 && response.config.timeout) {
break;
}
return retryRequest(response.config, response.status);
}
// otherwise
return $q.reject(response);
}
};
});
}
I know the question is already a little bit older, but I came upon it looking for a solution for the same problem. Other than in the answer already given I don't want to check a flag which I set at the same time as i cancle the AJAX request. It could already be a new request, which wasn't cancled.
So I found a different solution for me. Perhaps someone can make use of it, althoug it isn't an up to date question.
The quintessence of my solution is, to check not only the status. If the data is also null, it was a cancled request.
var defer = $q.defer()
$http.get("http://example.com/someUrl", {timeout: defer.promise})
.error(function(data, status){
if (data === null && status === 0) {
// the request was cancled
}
});
You can use (rejection.config.timeout.status != 499):
...timeout.promise.status = 499;
...timeout.resolve();
responseError: function(rejection) {
if(rejection.status == 0 && rejection.config.timeout.status != 499) {
alert("check your connection");
}
return $q.reject(rejection);
}
instead of:
responseError: function(rejection) {
if(rejection.status == 0 && !rejection.config.timeout) {
alert("check your connection");
}
return $q.reject(rejection);
}
By the way in this case you don't need additional request decorator.
I was able to determine if the request was cancelled in Chrome v55 by checking the global event object's property type against the string "abort" (code below). I had the problem that ongoing requests were cancelled when navigating to another page and triggered the responseError.
angular.module("services.interceptor", arguments)
.config(function($httpProvider) {
$httpProvider.interceptors.push(function($q, $window) {
return {
responseError: function(rejection) {
if($window.event.type === "abort") {
// handle cancelled here
} else if(rejection.status < 1) {
alert("check your connection");
}
return $q.reject(rejection);
}
};
});
});
});
(also: The status property seems to have changed from 0 to -1 for network errors, so I wrote rejection.status < 1))