I'm trying to add an abort function to my promise in Angular. When debugging the code, I can see the function gets added as expected. However, when the object is returned to the calling service, the function is no longer there. I'm hoping it's something trivial.
.factory('MatchmakerSearch', ['$resource', 'OBB_ENV_CONF', '$q', function ($resource,
OBB_ENV_CONF, $q) {
// Create the $resource object to handle the API requests
function _query(params) {
var _deferredAbort = $q.defer();
var _request = $resource(OBB_ENV_CONF.API_HOST + 'int/matchMaker', {}, {
'query': {
method: 'GET',
params: params,
isArray: false,
timeout: _deferredAbort.promise
}
});
var _promise = _request.query().$promise.then( // Convert from $resource to $http
function (response) {
return response;
}, function (response) {
return $q.reject('Ajax call aborted');
}
);
_promise.abort = function () {
_deferredAbort.resolve();
};
_promise.finally(function () {
_promise.abort = angular.noop;
_deferredAbort = _request = _promise = null;
});
return _promise; // <~~~~ abort function exists here
}
return {
query: _query
}
}
]);
The service making the call looks like this:
_searchRequest = MatchmakerSearch.query(buildQueryParams()).then(function (result) {
// <~~~~ _searchRequest does not contain an abort() function.
});
I really thought this would be a simple thing to code. Any ideas on why my function is disappearing on return?
Every time you chain a promise with then, catch, or finally you receive a new Promise back:
_searchRequest = MatchmakerSearch
.query(buildQueryParams()) // Your customised promise.
.then(function (result) {}) // a new promise returned here.
So _searchRequest ends up being a fresh new Promise instance.
The documentation for deferred.then() talks about this:
then(successCallback, errorCallback, notifyCallback) – ...
This method returns a new promise which is resolved or rejected via the return value of the successCallback, errorCallback ...
(Emphasis in the original)
Related
I have steps array like:
var stepsToDo = [step1, step2, step3,...]
every step does ajax call:
function step1() { return $.ajax({ ... }); }
function step2() { return $.ajax({ ... }); }
The sample ajax call for step 1 (step 2 and others are similar):
return $.ajax({
url: "test.php",
type: "POST",
dataType: "json",
data: {"step-1": ""},
beforeSend: function () {
writeResult("Processing-> test 1");
},
success: function (result) {
if (result) {
if (result instanceof Array) {
writeResult("Found: " + result.length + " items");
arr = result;
} else {
writeResult(result);
arr = [];
}
}
}
});
function writeResult(str) { console.log(str); }
I want to execute sequentially (defined in stepsToDo).
I tried like:
stepsToDo.reduce(
(promise, method) => {
return promise.then(method);
},
Promise.resolve()
);
Nothing happens, no print in console happens.
Why ?
Drop the new Promise. Your steps are functions that return promises, not promise executors - they never would call a resolve callback passed into them.
For the reducer function you initially created...
stepsToDo.reduce((promise, method) => {
return promise.then(_ => new Promise(method));
}, Promise.resolve());
...your step functions should resolve or reject the promises created in your reducer - therefore should implement the promise executor signature, I.E:
type executor = (resolve: (value?: T | PromiseLike<T>) => void, reject: (reason?: any) => void) => void;
For example:
function step1(resolve, reject) {
$
.ajax({ /* ... */ })
.done(resolve)
.fail(reject);
}
EDIT
As #Bergi states in the comments, this is an anti-pattern. Instead you could move the Promise construction into your step functions. A basic example (the jQuery ajax API still has to be converted to promises)
function step1() {
return new Promise((resolve, reject) => {
$.ajax({ /* ... */ }).done(resolve).fail(reject);
});
}
If all your steps return you promises your reducer can get much simpler:
stepsToDo.reduce((promise, method) => promise.then(method), Promise.resolve());
You could in this case even just return the result of $.ajax, since jQuery promises do implement a then method:
function step1() {
return $.ajax({ /* ... */ });
}
If a native promise is required for error handling, i.e. Promise.catch, you can explicitly cast the jQuery promise to a native promise with Promise.resolve:
function step1() {
return Promise.resolve($.ajax({ /* ... */ }));
}
I'm not sure what's wrong with the other answers but $.get (or ajax or post) will return a promise like.
So your step methods can look like this:
var stepOne = () => {
return $.ajax({//just return the promise like
url: "test.php",
type: "POST",
dataType: "json",
data: {"step-1": ""}
});
}
You can then reduce it to one promise that resolves with the results of the three steps:
[stepOne,stepTwo,stepThree].reduce(
(p,fn)=>
p.then(
results=>fn().then(result=>results.concat([result]))
),
$.Deferred().resolve([])
)
.then(
results=>console.log("got results:",results),
err=>console.warn("Something went wrong:",err)
);
You see I don't pass Promise.resolve as second argument to reduce but $.Deferred().resolve([]), this is jQuery promise like value. You can now support browsers that don't have native promises without the need to polyfill.
Although if you need to support those I'd recomment not using the arrow function syntax and use function(something){return something} instead of something=>something
There most be something missing from the original question or the implementation. If all the step functions return the$.ajax promise then the Array.reduce pattern should work.
Below is a working prove of concept using a step() function to simulate the asynchronous code execution and using the exact same Array.reduce pattern:
// Step generator
function step(number) {
return function() {
return $.Deferred(function(def) {
setTimeout(function() {
console.log('Running Step ', number);
def.resolve();
}, Math.floor(Math.random() * Math.floor(250)));
}).promise();
}
}
// List of steps
var stepsToDo = [step(1), step(2), step(3), step(4), step(5)];
// Consume steps one at a time
stepsToDo.reduce(
(promise, method) => {
return promise.then(method);
},
Promise.resolve()
).then(() => console.log('All Done!'));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
I am stuck on why my promises aren't working. From the other articles I have looked at, I think I am doing it correctly. Here is the code I have currently:
Factory code
factory.isLoggedIn = function() {
$http.get('/api/v1/fitbit/auth')
.success((data) => {
return data.status;
})
.error((error) => {
console.log('Error: ' + error);
});
}
Controller code
$scope.fitbitAuth = function() {
FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
return $scope.fitbitStatus;
};
From my understanding of promises, the return $scope.fitbitStatus should be populating the $scope.fitbitAuth, but it isn't. I am also returning a boolean in the Factory, which should be populating $scope.fitbitStatus.
You have to return something (the promise), or it is undefined.
Factory code:
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}
Controller code:
$scope.fitbitAuth = function() {
return FitbitFactory.isLoggedIn()
.then(
function(data) {
$scope.fitbitStatus = data;
},
function(errorData) {
console.log(errorData);
});
};
The complete success/error block in your factory is not necessary and should be removed. I'm also unsure why you return $scope.fitbitStatus; as it is undefined at the time of return.
Edit: Edited the answer to actually return the promise.
Currently you haven't return anything from isLoggedIn factory method and you are calling .then method over it.
To make it working return $http promiseobject from service method. In your case you could simply return$http.getmethod call which return promise object itself and then you can easily chain them up by callingFitbitFactory.isLoggedIn().then`
factory.isLoggedIn = function() {
return $http.get('/api/v1/fitbit/auth');
}
I'm trying to get my head around testing a parse.com backed data service, but have not found a satisfying solution so far. Basically, I want to use karma/jasmine to test a function returning a promise.
I'm spying on the query.get() method to intercept and return an object that was created in the implementation of the jasmine spec. However, when the spy returns the local object, the success path of the query.get options argument is not executed. Instead, the to-be-tested function directly returns the unresolved promise, which is not what I want. Basically, I want to test the success path (and the error path) of the data service.
Here's the code:
to-be-tested parse.com data services
angular.module('app.services',[])
.factory('AppServices',function(){
var doFunction1 = function(){
var promise = new Parse.Promise();
var ParseObjectClass = Parse.Object.extend('ParseObject');
var query = new Parse.Query(ParseObjectClass);
query.get('abc1234',{
success: function(result){
promise.resolve(result);
},
error: function(result, error){
promise.reject(error);
}
});
return promise;
}
return {
function1: doFunction1
}
});
jasmine spec
describe('MyServices Tests', function() {
var AppServices;
var scope;
beforeEach(function() {
module('app.services');
});
beforeEach(inject(function($rootScope, AppServices) {
AppServices = AppServices;
scope = $rootScope.$new();
Parse.initialize("key1", "key2");
var user = new Parse.User({
id: 'abc1234',
});
var ParseObjectClass = Parse.Object.extend('ParseObject');
var obj1 = new ParseObjectClass({
id: 'xyc789'
});
spyOn(Parse.Query.prototype, 'get')
.and.callFake(function(options) {
return Parse.Promise.as(obj1);
});
}));
it('parseMockTest', function() {
var result = AppServices.function1(2);
console.log(JSON.stringify(result));
});
});
This is the result from the karma log:
LOG: '{"_resolved":false,"_rejected":false,"_resolvedCallbacks":[],"_rejectedCallbacks":[]}'
I would have expected that the returned promise is resolved.
Any ideas/hints on how to test functions that return promises?
I would like to return a promise and an object called output either before or after the $http call. Can someone advise me how I can do this with the AngularJS framework and very important with Typescript so I can be sure it is working correctly?
topicNewSubmit = (): ng.IPromise<any> => {
var self = this;
var myData1 = { abc: 123 }
if (self.abc = 22) {
// How can I return an OKAY promise from here?
}
if (self.abc = 33) {
// How can I return an OKAY promise with myData1 from here?
}
if (self.abc = 88) {
// How can I return a FAIL promise from here?
}
return self.$http({ url: self.url, method: "GET" })
.then(
(response: ng.IHttpPromiseCallbackArg<any>): any => {
var myData2 = { abc: 245 }
// How can I return a promise and myData2.
// return(myData2) gives an error with Typescript
// How can I return a promise and no data
// return gives an error with Typescript
},
(error: ng.IHttpPromiseCallbackArg<any>): ng.IPromise<any> => {
var myData3 = { abc: 345 }
// Is this the correct way to return an unhandled reject with myData3
return self.$q.reject(myData);
});
}
Edit: Fixed the code and added a TypeScript Playground example. The methods are correctly typed, you can verify this by the typing errors that are thrown, try and fix them ;). I copied over the very basic required interfaces from the angular definition file.
Edit #2: Here's the fixed version of the TypeScript Playground example above.
If I understand your question correctly, you're trying to define a return type for the service method stating it returns a promise whose result upon resolving will be a certain object?
In that case you're almost there, I've split up your two example methods in separate blocks, as they require a different approach.
On a general note, I removed the scope copying (self = this) as you are using the fat arrow methods, which will automatically scope the method to the outer lexical scope. In short, there's no need to do the scope copy and in fact, in your example self doesn't always point to the service (as you're copying the scope inside of the method instead of outside of it).
Also, please note the definition of an Angular promise (truncated):
interface IDeferred<T> {
resolve(value?: T): void;
reject(reason?: any): void;
}
As such, typing an Angular Promise will only add a typing for the resolve case of the promise, not for the rejected case. Consequently, when calling your service, it will verify that the result in the success handler is of the type you have defined, but the type of the parameters in the error handler is of type any.
topicTest
For this method to work you need to inject $q into your service and then use it to create your own deferred
topicTest = (): IPromise<Foo> => { // return a promise which will result in a parameter of MyType upon resolving
var deferred = this.$q.defer<Foo>(); // Type the deferred to get better 'intellisense' support
if (this.abc = 99) {
deferred.resolve(new Foo());
}
if (this.abc = 88) {
deferred.reject("You can pass in whatever you like to the reject case");
}
return deferred.promise;
};
topicNewSubmit
The $http already returns promises, so you only need to hook into these by attaching a then callback and returning from that method to allow chaining other then callabacks to it.
In that case the return type of your service method would be angular.IPromise<() => any> where you can replace any with a type you'd like. The return type of the then method would have to correspond to whatever type you chose for the generic placeholder in the return type of the service method.
topicNewSubmit = () : IPromise<Foo> => {
return this.$http({ url: this.url, method: "GET" }).then((response): Foo => {
return new Foo();
}, (error) => {
return "whatever you'd like, it does not have to correspond to Foo";
});
}
You could then use your service like
MyService.topicNewSubmit().then((data) => {
// data needs to be of type T, where T is the type you defined in the generic placeholder of IPromise<T>
}, (error: any) => {
// In the error case, the parameters are of type any
});
To be honest, I feel like I'm shooting in the dark with your code sample, but here's my solution.
Like Anzeo, I removed the references to self or this. $q and $http should be assigned somewhere.
declare var $q: ng.IQService;
declare var $http: ng.IHttpService;
var topicNewSubmit = (): ng.IPromise<any> => {
var deferred = $q.defer<any>();
var myData1 = { abc: 123 };
if (this.abc = 22) {
deferred.resolve();
} else if (this.abc = 33) {
deferred.resolve(myData1);
} else if (this.abc = 88) {
deferred.reject();
} else {
$http({
url: this.url,
method: "GET"
})
.then(() => {
deferred.resolve({ abc: 245 });
}, () => {
deferred.reject({ abc: 345 });
});
}
return deferred.promise;
};
Answer
You cannot return something that isn't there yet unless you block all operations till you get it. Since the browser JavaScript is (mostly) single threaded you don't want to block the thread while the file downloads. Hence you return a promise that will eventually resolve. The consumer needs to use the then function to eventually get the value.
Humor
A promise is for life. Its 🐢s all the way down 🌹
You can use promise like
var deferred = $q.deferred();
$http(api url and method)
.success function() {
deferred.resolve();
}
.failure function() {
deferred.reject();
}
return deferred.promise;
I have moved some common code to factory. but the controller is executing before factory get loaded. In this case i am getting the blank response(zero results)
can anyone suggest the best solution.
here is my angular factory,
app.factory('TabsFactory', function($resource){
var activetabs = {};
activetabs.getDepositAccountDetails = function() {
return $resource('xxxx/:number', {}, {
getDepositAccountDetailsService: {
method: 'GET',
isArray: false
}
});
}
activetabs.getAccountInfo = function(){
return accountinit.accountInfo;
}
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
return activetabs;
});
controller,
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo);
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
You should use chain promise to update scope variable, because your accountInfo variable is updated inside $resource promise.
Code
TabsFactory.setAccountInfo(accountnumber, $scope.accountInfo).then(function(data){
$scope.accountInfo = TabsFactory.getAccountInfo();
alert(JSON.stringify($scope.accountInfo));
});
Update
Service method should return promise inorder to continue promise chain
activetabs.setAccountInfo = function(accountnumber, result) {
var accountinit = {
accountInfo: []
}
if (result.code == "v") {
//added return below
return activetabs.getDepositAccountDetails().getDepositAccountDetailsService({
number: accountnumber
}).$promise.then(function(response) {
accountinit.accountInfo = response;
return accountinit.accountInfo;
//here i am getting the JSON response
}, function(error) {
});
}
return accountinit;
}
Yes, this will happen because of JavaScript executing asynchronous operations but your controller in such a way that it expects things to be synchronous operations.
When you call TabsFactory.getAccountInfo() its possible that your $resource('xxxx/:number') is still not completed and response ready for you to process!!
So, what to do? You have make use of promise. I usually have a repository (A factory with method that return promise) to handle server communications. Here is an example:
app.factory('accountRepository', ["$http","$q",function($http,$q){
return {
getDepositAccountDetails : function(id) {
var deferred = $q.defer();
$http.ger('xxx').success(deferred.resolve).error(deferred.reject);
return deferred.promise;
}
};
}] );
My repository will have more operations like add account, update account info etc..
my controller/service then calls these methods as follows:
accountRepository.getDepositAccountDetails(123).then(function(response) {
// Process the response..
}, function(error) {
// Some error occured! handle it
});
doing so, my code gets executed only after I get response from server and data is ready for consumption or display. Hope this helps..
Update: You might want to have a look at this to get the idea ;)