I've been looking over and over for an example on how to cancel an ongoing REST-call using Angular's $resource. I haven't found any solution yet, but from the Angular documentation I got the impression that it should be possible.
From the documentation:
Usage:
$resource(url[, paramDefaults][, actions]);
One of the actions defined in the documentation:
timeout – {number|Promise} – timeout in milliseconds, or promise that should abort the request when resolved.
Does anyone have a working example showing how to use this timeout action with promise to cancel an ongoing request? Is it possible?
My code example:
var canceler = {};
$scope.doSomething = function() {
canceler = $q.defer();
$http.post('url', data, {timeout: canceler.promise}).
success(function(data) {
}).
error(function() {
});
};
function cancelPost() {
canceler.resolve(); //aborts request
}
}
Yes this is possible. You have to create a defere and set the promise as parameter:
var timeoutPromise = $q.defer();
{timeout: timeoutPromise.promise}
Then you can resolve the promise at any time:
timeoutPromise.resolve();
It should also be possible to call $timeout.cancel(timeoutPromise). What should be equal to timeoutPromise.reject().
$timeout $q
Related
I would like to test this function:
function initializeView() {
var deferred = $q.defer();
if(this.momentArray) {
core.listMoments(constants.BEST_MOMENT_PREFIX, '').then(function(moments) {
//Ommitted
deferred.resolve(moments);
}, function(error) {
console.log("ERROR");
deferred.reject(error);
});
}
else {
deferred.resolve();
}
return deferred.promise;
};
The function calls core.listMoments:
function listMoments(prefix, startAfter) {
// var deferred = $q.defer();
var promises = [];
return awsServices.getMoments(prefix, startAfter).then(function(moments) { //Mocked
console.log("getMoments Returned"); //Does not print
for(var i = 0; i < moments.length; i++) {
// moments[i].Key = constants.IMAGE_URL + moments[i].Key;
promises.push(getMomentMetaData(moments[i]));
}
return $q.all(promises);
});
};
Here is my test function:
it('Should correctly initialize the view', function(done) {
spyOn(awsServices, 'getMoments').and.callFake(function() {
console.log("getMoments Has been mocked"); //This prints
return $q.resolve(mock_moment);
});
service.initializeView().then(function() {
done();
})
});
The problem is with the awsServices 'getMoments' mock. The call to awsServices.getMoments is in the listMoments function. I would like to mock out this function but when I do it does not execute the "then" part of the promise.
So based on my console logs it would print the 'getMoments Has been mocked' log but it would not print 'getMoments Returned' log. So the function is mocked but for some reason it is not moving into the then statement and my test just times out.
In order to get the .then() part of a promise to work in such a test, you need to use a $rootScope.$apply(). This is needed whether the promise is in your test code or in a referenced library that is being tested. Think of it like the flush() function for $http or $timeout calls.
The Testing example from the Angular documentation's $q page shows how to use it:
it('should simulate promise', inject(function($q, $rootScope) {
var deferred = $q.defer();
var promise = deferred.promise;
var resolvedValue;
promise.then(function(value) { resolvedValue = value; });
expect(resolvedValue).toBeUndefined();
// Simulate resolving of promise
deferred.resolve(123);
// Note that the 'then' function does not get called synchronously.
// This is because we want the promise API to always be async, whether or not
// it got called synchronously or asynchronously.
expect(resolvedValue).toBeUndefined();
// Propagate promise resolution to 'then' functions using $apply().
$rootScope.$apply();
expect(resolvedValue).toEqual(123);
}));
Note that they inject $rootScope.
$q promises can be synchronous (when they are resolved synchronously) and depend on digest cycles.
There should generally be no asynchronous done callback in Angular tests.
Angular tests are supposed to be synchronous, so are $q promises. In order to achieve that a digest should be triggered manually when an existing promise (the ones that is returned from getMoments and initializeView) is chained with then. If done callback is placed inside then and a digest is not triggered, this will result in spec timeout.
spyOn(awsServices, 'getMoments').and.callFake(function() {
console.log("getMoments Has been mocked"); //This prints
return $q.resolve(mock_moment);
});
service.initializeView();
$rootScope.$digest();
The thing that can be improved here is isolation. There are several units (methods) involved in a single test. This will affect troubleshooting when one of them fails.
Usually unit testing implies that only one unit is tested at time, while the rest are mocked or stubbed. In this case in one test service.listMoments is called and awsServices.getMoments is mocked, and in another test service.initializeView is called and service.listMoments is mocked.
This question already has answers here:
AngularJS Promises, $q, defer
(2 answers)
Closed 5 years ago.
I am new to angularjs.I saw $q in restful api calls to check the promise.
$q.defer() was used to retain the promise object.
I read about the promises but I didn't get anything.
although I can make the api call without $q, however it is used somewhere in articles.
So I want to know the exact use of $q and difference in making api calls without $q.
Kindly help.
thanks
I think the article I wrote about $q might help you.
Introduction to $q
$q is an angular defined service. It’s the same as new Promise(). But $q takes things to the next level by enhancing additional feature that developers can use to perform complex tasks more simply.
This is a sample for creating a promise using $q
angular.module("app",[])
.controller("ctrl",function($scope,$q){
var work = "resolve";
var promise = $q(function(resolve, reject) {
if (work === "resolve") {
resolve('response 1!');
} else {
reject('Oops... something went wrong');
}
});
promise.then(function(data) {
alert(data)
})
})
$q.defer()
$q.defer() return the instance of the promise constructor. Once you create a defer object there are following methods and properties that you can access from that object
resolve(value) – resolves the derived promise with the value. If the value is a rejection constructed via $q.reject, the promise will be rejected instead.
reject(reason) – rejects the derived promise with the reason. This is equivalent to resolving it with a rejection constructed via $q.reject.
notify(value) - provides updates on the status of the promise's execution. This may be called multiple times before the promise is either resolved or rejected.
promise – {Promise} – promise object associated with this deferred
See the example
angular.module("app",[])
.controller("ctrl",function($scope,$q){
var work = "resolve";
function getData(){
var obj = $q.defer();
if (work === "resolve") {
obj.resolve('response 1!');
} else {
obj.reject('Oops... something went wrong');
}
return obj.promise;
}
getData().then(function(data) {
alert(data)
})
})
$q.all()
If a user need to send multiple request one shot,then the user can use $q.all() service.
$q.all([$http.get('data1.json'),$http.get('data2.json')])
.then(function(response){
console.log(response[0].data) // data1.json response
console.log(response[1].data) // data1.json response
})
In here,there are two http request sent simultaneously to two separate JSON files to get data. The response comes as an array and response order is same as the HTTP request order.
$q.race()
$q.race() is very similar to $q.all(). But instead of sending response of each request, it will only return the one request response. Specifically, only return the response of first request that been executed. That does not mean it’s not going to send other requests. All the requests are sending but it's only return the response of the first request that executed.
$q.race([$http.get('data1.json'),$http.get('data2.json')])
.then(function(response){
console.log(response[0].data) // return one response
})
In here response can be either data1.Json or data2.json. That's the downfall of using this method. Since its return the response of the first executed request, can’t be sure which request response will resolved by the promise. This method useful for bulk requests which you don’t want to see the response of all the requests
Conclusion
Use $q for constructing promises from non-promise Objects/callbacks, and utilize $q.all() and $q.race() to work with existing promises.
I like this question. Because, I too faced this.
This is a service that helps you run functions asynchronously, and use their return values when they are done processing.
Brief Description
Refer example
Promise with $q
Example :
app.service("githubService", function($http, $q) {
var deferred = $q.defer();
this.getAccount = function() {
return $http.get('https://api.github.com/users/haroldrv')
.then(function(response) {
// promise is fulfilled
deferred.resolve(response.data);
// promise is returned
return deferred.promise;
}, function(response) {
// the following line rejects the promise
deferred.reject(response);
// promise is returned
return deferred.promise;
});
};
});
I have a factory which wraps $http for custom template loading, and I'm trying to write unit tests for it. Valid templates work because they're pre-loaded into $templateCache, so the underlying http promise never needs to be called.
However, I'm trying to test our promise gets rejected if the file path is wrong.
I'm telling ngMock to respond with a 404:
$httpBackend.when('GET', '/invalid').respond(404, '');
My test never fires the then/catch/finally code. The underlying http promise is never resolved/rejected.
I've reduced the test to pure $http calls while resolving this issue.
it('"get" rejects with invalid path', function(done) {
var deferred = $http.get('/invalid');
deferred.catch(function(error) {
console.log(error);
done(error);
});
deferred.then(function(response) {
console.log(response);
done();
});
deferred.finally(function() {
console.log('finally!');
});
// Trigger http promise resolution
$rootScope.$digest();
});
In order to trigger the responses to requests, call $httpBackend.flush();. This will also trigger a digest cycle, so you won't need to call .$apply();.
Also, the response from an $http is a modified promise. I've updated the code below to handle your use case.
Lastly, make sure that you are using the ngMock module and not the ngMockE2E. They each give you a slightly different $httpBackend object, but the ngMock module is meant for unit testing. See the differences here (ngMock's $httpBackend vs ngMockE2E's $httpBackend)
Here is a simplified fiddle showing how it works.
Your updated code would be:
it('"get" rejects with invalid path', function() {
$httpBackend.whenGET('/invalid').respond(404, '');
var wasRejected = false;
$http.get('/invalid')
.error(function (data, status, headers, config) {
console.log(status);
wasRejected = true;
});
// Trigger http promise resolution
$httpBackend.flush();
// Make your assertion...
expect(wasRejected).toBeTrue();
});
afterEach(function(){
$httpBackend.verifyNoOutstandingExpectation();
$httpBackend.verifyNoOutstandingRequest();
});
You have a couple problems with your code. First $http promises are resolved using success, error and then. From the angular documentation here:
$http Returns a promise object with the standard then method and two http specific methods: success and error. The then method takes two arguments a success and an error callback which will be called with a response object. The success and error methods take a single argument - a function that will be called when the request succeeds or fails respectively. The arguments passed into these functions are destructured representation of the response object passed into the then method.
The second is that you need to call $httpBackend.flush(); to trigger the return of your request. This is to simulate an asynchronous behavior in a synchronous test.
it('Should return status Not Found', function() {
$httpBackend.expectGET('/invalid').respond(404, 'Not Found');
var result = '';
$http.get('/invalid')
.success(function(data){
result = data;
}).error(function(reason){
result = reason;
});
$httpBackend.flush();
expect(result).toEqual('Not Found');
});
});
I'd like to delay the response to the following whenGET:
$httpBackend.whenGET(/^foobar/).respond(function () {
return [200,{}];
});
However it seems impossible using $timeout to do this synchronously, so I'm not sure how to approach this?
If you want to delay only a specific response, than you may delay assignment of the reponse to scope property.
If you wrap your call into your custom service method, than you may wrap the response into the promise and resolve it when needed:
JS:
angular.module('app').service('myService', function($q, $timeout){
this.getData = function() {
var delay = 300,
origPromise = $http.get('someUrl'),
deferred = $q.defer();
$timeout(function() {
deferred.resolve(origPromise);
}, delay);
return defered.promise;
};
});
[EDIT]:
To set the delay to all requests, you may apply this solution to the response interceptor
I am trying to unit test a service which has asynchronous methods but am having no luck.
I have tried to implement with promises by using the $q support in angularjs.
Any help would be appreciated.
http://jsfiddle.net/9pBze/37/
angular.module('myapp', ['myservice']);
angular.module('myservice', []).factory('myservice', function($q) {
var ls = {};
ls.DoIt = function() {
var deferred = $q.defer();
setTimeout(function(){
deferred.resolve(5);
},3000);
return deferred.promise;
}
return ls;
});
describe('services', function () {
beforeEach(module('myservice'));
it("should equal 2", inject(function(myservice) {
myservice.DoIt().then(function(returned) {
expect(returned).toEqual(2);
});
}));
});
First of all, the setTimeout is particularly tricky to test since it hard to mock. Fortunately AngularJS has a wrapper around it ($timeout) that plays the same role but can be easily mocked:
ls.DoIt = function() {
var deferred = $q.defer();
$timeout(function(){
deferred.resolve(5);
},3000);
return deferred.promise;
}
The mock provided for $timeout allows us to easily simulate elapsed time (with $timeout.flush()) which means our tests can run fast, without really waiting for the async event to complete (please note that the production code is still using async API!).
The changed tests would look like:
it("should equal 5", inject(function(myservice, $timeout) {
var valueToVerify;
myservice.DoIt().then(function(returned) {
valueToVerify = returned;
});
$timeout.flush();
expect(valueToVerify).toEqual(5);
}));
And finally the working jsFiddle: http://jsfiddle.net/v9L9G/1/
It's not related to Angular itself, but to Jasmine async tests.
If you need a setTimeout use Angular $timeout. And if you wish to have a fine control over setTimeout/$timeout executions, use mocked Clock.