Clearing setTimeout when $http call is in pending - javascript

function getDBCounts() {
$http.get('URL')
.then(function (response){
$scope.stats = response.data;
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}, function () {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
});
when ever I am going to different controller I am making sure I am cancelling all timeouts but the issue is when the $http call is in pending and I am moving to different controller at the same time (the timeout is not being created until we get the response) and I am unable to cancel that timeout on controller change since the call is in pending state and I cannot clear a timeout which is not been created.
How do I handle this situation. what is the best solution for this issue.
I have done this but in the error section I am unable to differentiate the network error and cancelled timeout because I need to still call setTimeout if it is network error.
$scope.canceler = $q.defer();
function getDBCounts() {
$http
.get(apiUri + '/backend/database/stats', {timeout: $scope.canceler.promise})
.then(function (response){
$scope.stats = response.data;
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}, function (er, second) {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
$scope.canceler.resolve();
});
For both network error and timeout i get this as a response:
{config: Object
data: null
headers: (name)
status: 0
statusText: ""}
Now how do I resolve this issue.
Thanks in advance.

This seems to be a limitation of the $http API. I would also have expected that you can tell from the response/error, whether the request has timed out.
The only way I see here is that you don't cancel the promise but set a flag in the scope when it has been destroyed. Then you simply don't set the timeouts when the scope has already been destroyed:
function getDBCounts() {
$http.get('URL')
.then(function (response){
$scope.stats = response.data;
})
.finally(function() {
if (!$scope.destroyed) {
$scope.getDBCountsTimeOut = setTimeout(getDBCounts, 5000);
}
})
}
$scope.$on("$destroy", function () {
clearTimeout($scope.getDBCountsTimeOut);
$scope.destroyed = true;
});
Your approach is better and would be the "correct" way. However, it seems this is not possible.
PS: If you want to do something regardless of whether a promise has been resolved or rejected, you should use finally (see above). Please note that in older browser (and thus older JS/ECMA-Script versions) finally is a reserved word. If you want to make sure that older browsers are supported, call it like this:
$http.get(...)["finally"](function() {
})

Related

How to delay a $httprequest?

I am trying to to delay my $http call like this:
githubService.getUserEvents = function getUserEvents() {
return $timeout(
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
}),
5000);
};
When I run this it does not seem to perform the delayed request and I cannot see any errors in the chromeconsole? See also here jsbin
How can I call a delayed $http request without using an interceptor?
jsbin
Deleted answer had this right I think, you do need a deferred since you can't return from the $timeout, also as comment indicated needed to inject $timeout service.
githubService.getUserEvents = function getUserEvents() {
var deferred = $q.defer();
$timeout(
function(){
$http.get(ANGULAR_EVENTS).then(function (eventsData) {
deferred.resolve(_.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
}));
})
},2000);
return deferred.promise;
};
Looks like I went over the public API limit here but should work.
EDIT
Per comments tried with just returning the $timeout promise and it does seem to work here (didn't seem to work when I tried that at first yesterday even after fixing the $timeout, so not sure what was wrong but here's the function working without making an extra deferred object)
githubService.getUserEvents = function getUserEvents() {
return $timeout(function(){
return $http.get(ANGULAR_EVENTS).then(function (eventsData) {
return _.map(eventsData.data, function (data) {
return {
type: data.type,
user: data.actor.login,
avatarUrl: data.actor.avatar_url,
createdOn: data.created_at,
repo: data.repo.name
};
});
})
},2000);
};
Still I have to stand by my point in the comments. So long as you are aware making your own defer is going to have some overhead and you are resolving whatever without going through the whole promise chain then I don't see an issue with doing it the first way (and I find it easier to understand). However just for clarification the $timeout promise does resolve with the value returned from the function it triggers (this makes sense but the docs made it sound as though $timeout resolved the moment the function was triggered not when it was complete... didn't get to the return part of the docs though tbh).

Nightwatch correct way to get api result to populate variable then run tests that use that variable

I am new to node and the async way of doing things.
I want to create and run a test suite using nightwatch.js, I have read all the docs and I'm baffled at how to do what I want (been working on it for 3 days already).
Am I thinking about this the wrong way?
module.exports = {
before: function(browser) {
/*
Here I just want to make a web call to an api and get a result and then
store that result in a variable which we will use later in test1 and other test cases
*/
browser.globals.myVariable = resultofsomeapicalll;
//wait here until proceeding
},
after: function(browser) {
browser.end();
},
beforeEach: function(browser) {
},
afterEach: function() {
},
'test1': function(browser) {
browser.url(browser.launchUrl + browser.globals.myVariable, function(result) {
browser.waitForElementPresent('body', 1000);
browser.expect.element("#something").to.be.present;
browser.saveScreenshot('./screenshots/' + browser.currentTest.module + '/' + browser.currentTest.name + '.png');
});
},
};
To perform asynchronous task in the Nightwatch.JS before[Each] or after[Each] hooks, you need to pass an callback argument to the function, which you trigger once the job is done.
In below example, it would be an API Call using Axios library;
module.exports = {
before: function(browser, done) {
axios.get('https://example.com/api?ID=12345')
.then(function (response) {
browser.globals.myVariable = response;
done();
})
.catch(function (error) {
done(error);
});
},
after: function(browser) {
browser.end();
},
beforeEach: function(browser) {
},
afterEach: function() {
},
'test1': function(browser) {
console.log()
},
};
Controlling the done invocation timeout
By default the done invocation timeout is set to 10 seconds (2 seconds
for unit tests). In some cases this might not be sufficient and to
avoid a timeout error, you can increase this timeout by defining an
asyncHookTimeout property (in milliseconds) in your external globals
file (see below for details on external globals).
http://nightwatchjs.org/guide/#asynchronous-before-each-and-after-each-
Best regards,
Riku

ui-router, resolve, $http not working as documented

I have bug (or maybe wrong usage?) with ui-router, resolve, factory and $http.get call.
Here's a snippet of the code in the config section:
$stateProvider
.state('index', {
url: '/',
views: {
'': {
templateUrl: './views/layout.html',
controller: 'MyAppCtrl'
},
'app-navbar#index': {
templateUrl: './views/app-navbar.html'
},
'app-accordion#index': {
templateUrl: './views/app-accordion.html',
controller: 'AppController',
resolve: {
appPromiseObj: function (AppFactory) {
return AppFactory.getApps();
}
}
},
...
and have the following AppFactory
myApp.factory('AppFactory', function ($http) {
var appFac = {
apps: []
};
appFac.getApps = function () {
promiseObj = $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
});
return promiseObj;
};
return appFac;
});
But when I run the app, the console.log message in the 'success' callback never gets executed. The browser console log shows the http call executes OK with code 200. I am assuming this means angular thinks it has failed or should I be doing something else?
I even tried returning the $q promise object (as suggested in other somewhat related stack overflow threads) but no success. In the factory code if I use test data (i.e., no HTTP call) everything works fine even if I don't return a promise object. Any pointer on where the problem could be? Appreciate any pointers to help me debug...
I created working plunker here. The problem was incorrect promise handling inside of the AppFactory.getApps(). We need to return the promise at the begining and then also return some adjusted stuff on success. Now it works...
This is the main change I made:
// INSTEAD of this
// appFac.getApps1 = function () {
// promiseObj = $http.get('http://localhost:4567/applications')
// .success(function (data) {
// console.log("success calling http");
// angular.copy(data, appFac.apps);
// });
//
// return promiseObj;
// Let's use this
appFac.getApps = function () {
return $http
.get('http://localhost:4567/applications')
.success(function (data) {
console.log("success calling http");
angular.copy(data, appFac.apps);
return appFac.apps
});
// this is already returned above
//return promiseObj;
Check it in action here
EXTEND
Based on your extended plunker (still not fully working as expected)
-http://plnkr.co/edit/c89j3eFvYyguMt0QznAI?p=preview
I created adjsuted and workin version
http://plnkr.co/edit/f2aucPcbtzqwIEogbjuJ?p=preview
The only changes was proper naming (e.g. app.js to be loaded as a script instead of script.js...). But at the end, the promise is now resolved and this json:
[{"id":10566982,"networkID":34256899,"appID":56114114
,"name":"10566982name","description"
...
]
Is loaded and converted into accordion:
56114114name
58616695name
Finally to answer your question in the comment below:
but what is the difference between promiseObj = $http(...)... ; return promiseObj and return $http (...); ?
There is no difference (except I see my approach a bit more clear). The real difference is:
angular.copy(data, appFac.apps);
vs
return appFac.apps
as a final statement of the .success() method. It MUST return something. tha's the trick

Can I act on and then forward the results of a AngularJS $http call without using $q?

I have functions like the getData function below.
I understand that $http returns a promise. In my current set up I am using $q so that I can do some processing of the results and then return another promise:
var getData = function (controller) {
var defer = $q.defer();
$http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
defer.resolve({
data: data,
dataPlus: dataPlus
});
})
.error(function (error) {
defer.reject({
data: error
});
});
return defer.promise;
}
Is there any way that I can do this without needing to use the AngularJS $q (or any other $q implementation) or is the code above the only way to do this? Note that I am not looking for a solution where I pass in an onSuccess and an onError to the getData as parameters.
Thanks
As you say $http.get already returns a promise. One of the best things about promises is that they compose nicely. Adding more success, then, or done simply runs them sequentially.
var getData = function (controller) {
return $http.get('/api/' + controller + '/GetData')
.success(function (data) {
var dataPlus = [{ id: 0, name: '*' }].concat(data);
return {
data: data,
dataPlus: dataPlus
};
})
.error(function (error) {
return {
data: error
};
});
}
This means that using getData(controller).then(function (obj) { console.log(obj) });, will print the object returned by your success handler.
If you want you can keep composing it, adding more functionality. Lets say you want to always log results and errors.
var loggingGetData = getData(controller).then(function (obj) {
console.log(obj);
return obj;
}, function (err) {
console.log(err);
return err;
});
You can then use your logging getData like so:
loggingGetData(controller).then(function (obj) {
var data = obj.data;
var dataPlus = obj.dataPlus;
// do stuff with the results from the http request
});
If the $http request resolves, the result will first go through your initial success handler, and then through the logging one, finally ending up in the final function here.
If it does not resolve, it will go through the initial error handler to the error handler defined by loggingGetData and print to console. You could keep adding promises this way and build really advanced stuff.
You can try:
Using an interceptor which provides the response method. However I don't like it, as it moves the code handling the response to another place, making it harder to understand and debug the code.
Using $q would be the best in that case IMO.
Another (better ?) option is locally augmented transformResponse transformer for the $http.get() call, and just return the $http promise.

AngularJS : promises, can you pass a promise back after using .then()?

I'm still new to Angular and promises so I hope I have the correct idea here.
I currently have a data layer service which uses restangular to get some data, then returns a promise, like this...
dataStore.getUsers = function (params) {
return users.getList(params);
};
Then, my controller which has called this function receives a promise back, like this...
$dataStore.getUsers(params).then(function (response) {
$scope.users = response;
}, function(response) {
$log.error("Get users returned an error: ", response);
});
This is working well, but I'd like to use the promise inside of my datastore before passing it back. I'd like to use the .then() method to check if it failed and do some logging, then, from the sucess function and from the failure function I'd like to return the original promise back to my controller.
My controller would then be able to use the .then() method like it already is, in fact, I don't want my controller code to change at all, just my datastore code.
Here's some semi-pseudo code to show what I'd like my datastore function to do...
dataStore.getUsers = function (params) {
users.getList(params).then(function (response) {
$log("server responded")
return original promise;
}, function(response) {
$log.error("server did not respond");
return original promise;
});
};
You were actually not far off at all in your pseudo code. Promises chain:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}, function(failure) {
$log.error("server did not respond");
// change to throw if you want Angular lever logs
return $q.reject(failure);
});
};
The controller now gets resolved/rejected with the same value. The log requires tapping into the promise so you must add a .then handler to deal with it. Other promise libraries have convinicene methods for this but $q is minimalistic in this regard.
Alternatively, you can use nicer catch syntax, as well as propagate the errors to your logs:
dataStore.getUsers = function (params) {
return users.getList(params).then(function (response) {
$log("server responded")
return response;
}).catch(function(failure) {
$log.error("server did not respond");
throw failure;
});
};

Categories