Callback function is not called only once in Service using $timeout - javascript

I'm trying to create a service similar to this example. My code is the following:
app.service('Poller', function ($q, $http, $timeout) {
var notification = {};
notification.poll = function (callback, error) {
return $http.get('https://someapi.com').then(function (response) {
if (typeof response.data === 'object') {
if (callback){
callback(response.data);
console.log('tick');
}
} else {
if (error) {
error(response.data);
}
}
$timeout(notification.poll, 10000);
});
}
notification.poll();
return notification;
});
And I try to use it in my controller like this:
Poller.poll(
function(jsonAPI) {
console.log(jsonAPI);
},
function(error) {
console.log('Error:', error);
}
);
The data are being fetched correctly but there seems to be two problems.
The callback function is called only once and not according to the $timeout. I added the conditionals in the service for callback end error because without them it throws an error callback is not a function. When I refresh or change the view the callback function is again called.
The $timeout seems to be triggered twice every 10 seconds and not once.

Use
$timeout(function () {
notification.poll(callback, error);
}, 10000);
instead of
$timeout(notification.poll, 10000);

Related

How to make http call in service but get response by the controller? AngularJS

I`ve got a service where I call http,the problem is that I dont understand how to use $scope.photos in the controller.How to config controller and service to do this?
app.service('http',function($scope,$http){
function getAll() {
$http.get('/allphotos').then(function success(response) {
$scope.photos = response.data;
}, function error(err) {
console.log('error is:' + err.err)
});
}
});
app.controller('ctrl',function ($scope, http) {
}
First of all never use $scope inside Service/Factory/Provider. The scope is related to controllers only, a.e. deals with bind views with controllers.
You have a method getAll() that should return Promise and you will be able to resolve it inside your controller.
Something like:
app.service('httpService',function(,$http){
this.getAll = function() {
return $http.get('/allphotos').then(function(response) {
// ^ 'getAll' returns Original Promis comes from '$http'
return response.data;
// ^ here we resolve 'response.data'
}, function(err) {
console.log('error is:' + err.err)
});
}
});
And now in controller do:
app.controller('ctrl',function ($scope, httpService) {
httpService.getAll().then(function(data) {
$scope.data = data;
}, function error(err) {
console.log('error is:' + err.err)
});
}

Testing asynchronous Ajax call

I am trying to have the function GetUsername to call the actual implementation and return the username if it is found back into the variable result. I am using Jasmine's done function but the test is not correct. It keeps on passing even if the expected and actual value are not the same. Any help or suggestion would be great! Thanks in advance.
Here are my objects:
var Git = {
VerifyGitUser: function (username, callback) {
$.ajax({
url: 'https://api.github.com/users/' + username
})
.done(function (data) {
callback.call(this, data);
})
.fail(function (data) {
callback.call(this, data);
})
}
}
var User = {
GetUsername: function (username) {
Git.VerifyGitUser(username, function (data) {
if (data.login) {
return data.login;
} else {
return null;
}
});
}
}
Here is my test:
describe('User', function () {
it('should return username', function (done) {
spyOn(Git, 'VerifyGitUser');
spyOn(User, 'GetUsername').and.callThrough();
var result = User.GetUsername('test');
done();
expect(Git.VerifyGitUser).toHaveBeenCalledWith('test');
expect(User.GetUsername).toHaveBeenCalled();
expect(result).toEqual('test');
})
});
There is currently no way to retrieve the data from User.GetUsername whenever it completes, so it will just return undefined. Plus your call to done() is completing the test before you assert anything with expect.
You can have User.GetUsername take a callback, just like you are doing for Git.VerifyGitUser:
var User = {
GetUsername: function (username, callback) {
Git.VerifyGitUser(username, function (data) {
if (data.login) {
callback(data.login);
} else {
callback(null);
}
});
}
}
Now you know when User.GetUserName has completed. So in your test, you can pass User.GetUserName a callback which can call done():
describe('User', function () {
it('should return username', function (done) {
spyOn(Git, 'VerifyGitUser');
spyOn(User, 'GetUsername').and.callThrough();
User.GetUsername('test', function(result) {
expect(Git.VerifyGitUser).toHaveBeenCalledWith('test');
expect(User.GetUsername).toHaveBeenCalled();
expect(result).toEqual('test');
done();
});
})
});
Other thoughts:
Do you need to call the actual API during this test? You can look into returning mock API data from VerifyGitUser using Jasmine spies.
In VerifyGitUser I don't see why you need to force context using callback.call(this, data). It seems like callback(data) is enough.
You may want to look into returning promises from async functions, instead of having them take in callbacks.

How to reload a http.get request after performing a function

I am trying to delete a post from a list. The delete function is performing by passing serially to a delete function showed below.
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) { });
}
After performing the function, I need to reload the http.get request which used for listing the list.
$http.get("api/phone_accept.php")
.then(function (response) { });
Once the function performed. The entire list will reload with new updated list. Is there any way to do this thing.
Try this
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) {
//on success of first function it will call
$http.get("api/phone_accept.php")
.then(function (response) {
});
});
}
function list_data() {
$http.get("api/phone_accept.php")
.then(function (response) {
console.log('listing');
});
}
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref)
.success(function (data) {
// call function to do listing
list_data();
});
}
Like what #sudheesh Singanamalla says by calling the same http.get request again inside function resolved my problem.
$scope.go = function(ref) {
$http.get("api/phone_recev.php?id="+ref).success(function (data) {
//same function goes here will solve the problem.
});}
});
You can use $q - A service that helps you run functions asynchronously, and use their return values (or exceptions) when they are done processing.
https://docs.angularjs.org/api/ng/service/$q
Inside some service.
app.factory('SomeService', function ($http, $q) {
return {
getData : function() {
// the $http API is based on the deferred/promise APIs exposed by the $q service
// so it returns a promise for us by default
return $http.get("api/phone_recev.php?id="+ref)
.then(function(response) {
if (typeof response.data === 'object') {
return response.data;
} else {
// invalid response
return $q.reject(response.data);
}
}, function(response) {
// something went wrong
return $q.reject(response.data);
});
}
};
});
function somewhere in controller
var makePromiseWithData = function() {
// This service's function returns a promise, but we'll deal with that shortly
SomeService.getData()
// then() called when gets back
.then(function(data) {
// promise fulfilled
// something
}, function(error) {
// promise rejected, could log the error with: console.log('error', error);
//some code
});
};

Angular Factory returns null

I'm trying to build a database in my angular factory:
angular.module("App")
.factory("DatabaseFactory", function () {
var database = null;
var factory = {};
factory.getDatabase = function () {
if (database == null) {
window.sqlitePlugin.openDatabase({
name: "myDB.db",
androidDatabaseImplementation: 2,
androidLockWorkaround: 1
}, function (db) {
database = db;
database.transaction(function (transaction) {
transaction.executeSql(create_user, [], function (transaction, result) {
console.log("table user created: " + JSON.stringify(result));
}, function (error) {
console.log("table user error: " + error.message);
});
}, function (error) {
console.log("transaction error: " + error.message);
}, function () {
console.log("transaction ok");
return database;
});
});
} else {
return database;
}
}
return factory;
});
The creation of the database works, the transaction is also ok. I now provide a service with a function to init the database:
angular.module("App")
.service("DatabaseService", function (DatabaseFactory) {
var database;
function initDatabase() {
console.log("init before database: " + JSON.stringify(database));
database = DatabaseFactory.getDatabase();
console.log("intit after database: " + JSON.stringify(database));
}
return {
initDatabase: function () {
initDatabase();
}
};
});
It gets called on device ready:
angular.module("App", ["ionic", "ngCordova", "App.Home"])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider.state("app", {
url: "/app",
abstract: true,
templateUrl: "templates/main.html"
});
$urlRouterProvider.otherwise("/app/home");
})
.run(function ($rootScope, $ionicPlatform, DatabaseService) {
$ionicPlatform.ready(function () {
console.log("ionic Ready");
if (window.cordova && window.cordova.plugins.Keyboard) {
cordova.plugins.Keyboard.hideKeyboardAccessoryBar(true);
}
if (window.StatusBar) {
StatusBar.styleDefault();
}
DatabaseService.initDatabase();
});
});
The log output:
init before database: + undefined
init after database: + undefined
So the return of the database in my factory returns undefined, but I don't know why. It should return the database since it is correctly initialized.
You can't return the database from the function, because the function that receives it is an asynchronous callback.
You can only use the return statement if the entire function is synchronous (e.g. doesn't do any async work, such as reading from files, connecting to databases, network requests, sockets etc).
In your case, window.sqlitePlugin.openDatabase does some asynchronous work and asks for a callback as the second argument. This callback will be called after the database connection has opened, which will be after your getDatabase function has returned a value.
window.sqlitePlugin.openDatabase({
name: "myDB.db",
androidDatabaseImplementation: 2,
androidLockWorkaround: 1
}, function (db) {
database = db
// this happens at some point in the future
});
// this happens straight away
// at this point database is still undefined
return database;
A good way to test this for future reference is to use console.log to see at what time and in what order your code is run.
window.sqlitePlugin.openDatabase({
// ...
}, function (db) {
database = db
console.log('A');
});
console.log('B');
return database;
You would see that rather than being executed in the order the statements are written, B is logged first, then A second.
If you make your getDatabase method take a callback argument, you can pass the db object into it as soon as it is ready.
factory.getDatabase = function (callback) {
window.sqlitePlugin.openDatabase({
// ...
}, function (db) {
// do some stuff with db, when you are ready
// pass it to the callback, with null as the
// first argument (because there isn't an error
callback(null, db);
});
Then you would rewrite your code to make use of the callback.
DatabaseFactory.getDatabase(function(err, db) {
console.log("intit after database: " + JSON.stringify(database));
});
You might be wondering why the callback has an err argument too.
In node.js, it is considered standard practice to handle errors in asynchronous functions by returning them as the first argument to the current function's callback. If there is an error, the first parameter is passed an Error object with all the details. Otherwise, the first parameter is null.
(From NodeJitsu)
I think you should replace
var database = null;
var factory = {};
by
var factory = {};
and do
return factory.database
in your factory.getDatabase

Setting a timeout handler on a promise in angularjs

I'm trying to set a timeout in my controller so that if a response isn't received in 250ms it should fail. I've set my unit test to have a timeout of 10000 so that this condition should be met,Can anyone point me in the right direction? ( EDIT I'm trying to achieve this without using the $http service which I know provides timeout functinality)
(EDIT - my other unit tests were failing because I wasn't calling timeout.flush on them, now I just need to get the timeout message kicking in when an undefined promise is returned by promiseService.getPromise(). I've removed the early code from the question) .
promiseService (promise is a test suite variable allowing me to use different behaviour for the promise in each test suite before apply, eg reject in one, success in another)
mockPromiseService = jasmine.createSpyObj('promiseService', ['getPromise']);
mockPromiseService.getPromise.andCallFake( function() {
promise = $q.defer();
return promise.promise;
})
Controller function that's being tested -
$scope.qPromiseCall = function() {
var timeoutdata = null;
$timeout(function() {
promise = promiseService.getPromise();
promise.then(function (data) {
timeoutdata = data;
if (data == "promise success!") {
console.log("success");
} else {
console.log("function failure");
}
}, function (error) {
console.log("promise failure")
}
)
}, 250).then(function (data) {
if(typeof timeoutdata === "undefined" ) {
console.log("Timed out")
}
},function( error ){
console.log("timed out!");
});
}
Test (normally I resolve or reject the promise in here but by not setting it I'm simulating a timeout)
it('Timeout logs promise failure', function(){
spyOn(console, 'log');
scope.qPromiseCall();
$timeout.flush(251);
$rootScope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
First, I would like to say that your controller implementation should be something like this:
$scope.qPromiseCall = function() {
var timeoutPromise = $timeout(function() {
canceler.resolve(); //aborts the request when timed out
console.log("Timed out");
}, 250); //we set a timeout for 250ms and store the promise in order to be cancelled later if the data does not arrive within 250ms
var canceler = $q.defer();
$http.get("data.js", {timeout: canceler.promise} ).success(function(data){
console.log(data);
$timeout.cancel(timeoutPromise); //cancel the timer when we get a response within 250ms
});
}
Your tests:
it('Timeout occurs', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(251); //timeout occurs after 251ms
//there is no http response to flush because we cancel the response in our code. Trying to call $httpBackend.flush(); will throw an exception and fail the test
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
$scope.qPromiseCall();
$timeout.flush(230); //set the timeout to occur after 230ms
$httpBackend.flush(); //the response arrives before the timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
})
DEMO
Another example with promiseService.getPromise:
app.factory("promiseService", function($q,$timeout,$http) {
return {
getPromise: function() {
var timeoutPromise = $timeout(function() {
console.log("Timed out");
defer.reject("Timed out"); //reject the service in case of timeout
}, 250);
var defer = $q.defer();//in a real implementation, we would call an async function and
// resolve the promise after the async function finishes
$timeout(function(data){//simulating an asynch function. In your app, it could be
// $http or something else (this external service should be injected
//so that we can mock it in unit testing)
$timeout.cancel(timeoutPromise); //cancel the timeout
defer.resolve(data);
});
return defer.promise;
}
};
});
app.controller('MainCtrl', function($scope, $timeout, promiseService) {
$scope.qPromiseCall = function() {
promiseService.getPromise().then(function(data) {
console.log(data);
});//you could pass a second callback to handle error cases including timeout
}
});
Your tests are similar to the above example:
it('Timeout occurs', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(251); //set it to timeout
$scope.$apply();
expect(console.log).toHaveBeenCalledWith("Timed out");
//expect($timeout.cancel).not.toHaveBeenCalled();
//I also use $timeout to simulate in the code so I cannot check it here because the $timeout is flushed
//In real app, it is a different service
})
it('Timeout does not occur', function() {
spyOn(console, 'log');
spyOn($timeout, 'cancel');
$scope.qPromiseCall();
$timeout.flush(230);//not timeout
$scope.$apply();
expect(console.log).not.toHaveBeenCalledWith("Timed out");
expect($timeout.cancel).toHaveBeenCalled(); //also need to check whether cancel is called
})
DEMO
The behaviour of "failing a promise unless it is resolved with a specified timeframe" seems ideal for refactoring into a separate service/factory. This should make the code in both the new service/factory and controller clearer and more re-usable.
The controller, which I've assumed just sets the success/failure on the scope:
app.controller('MainCtrl', function($scope, failUnlessResolvedWithin, myPromiseService) {
failUnlessResolvedWithin(function() {
return myPromiseService.getPromise();
}, 250).then(function(result) {
$scope.result = result;
}, function(error) {
$scope.error = error;
});
});
And the factory, failUnlessResolvedWithin, creates a new promise, which effectively "intercepts" a promise from a passed in function. It returns a new one that replicates its resolve/reject behaviour, except that it also rejects the promise if it hasn't been resolved within the timeout:
app.factory('failUnlessResolvedWithin', function($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
$q.when(func()).then(function(results) {
deferred.resolve(results);
}, function(failure) {
deferred.reject(failure);
});
return deferred.promise;
};
});
The tests for these are a bit tricky (and long), but you can see them at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview . The main points of the tests are
The tests for the controller mocks failUnlessResolvedWithin with a call to $timeout.
$provide.value('failUnlessResolvedWithin', function(func, time) {
return $timeout(func, time);
});
This is possible since 'failUnlessResolvedWithin' is (deliberately) syntactically equivalent to $timeout, and done since $timeout provides the flush function to test various cases.
The tests for the service itself uses calls $timeout.flush to test behaviour of the various cases of the original promise being resolved/rejected before/after the timeout.
beforeEach(function() {
failUnlessResolvedWithin(func, 2)
.catch(function(error) {
failResult = error;
});
});
beforeEach(function() {
$timeout.flush(3);
$rootScope.$digest();
});
it('the failure callback should be called with the error from the service', function() {
expect(failResult).toBe('Not resolved within 2');
});
You can see all this in action at http://plnkr.co/edit/3e4htwMI5fh595ggZY7h?p=preview
My implementation of #Michal Charemza 's failUnlessResolvedWithin with a real sample.
By passing deferred object to the func it reduces having to instantiate a promise in usage code "ByUserPosition". Helps me deal with firefox and geolocation.
.factory('failUnlessResolvedWithin', ['$q', '$timeout', function ($q, $timeout) {
return function(func, time) {
var deferred = $q.defer();
$timeout(function() {
deferred.reject('Not resolved within ' + time);
}, time);
func(deferred);
return deferred.promise;
}
}])
$scope.ByUserPosition = function () {
var resolveBy = 1000 * 30;
failUnlessResolvedWithin(function (deferred) {
navigator.geolocation.getCurrentPosition(
function (position) {
deferred.resolve({ latitude: position.coords.latitude, longitude: position.coords.longitude });
},
function (err) {
deferred.reject(err);
}, {
enableHighAccuracy : true,
timeout: resolveBy,
maximumAge: 0
});
}, resolveBy).then(findByPosition, function (data) {
console.log('error', data);
});
};

Categories