How to get .notify() in chaining promises from $q.all? - angularjs

I am using chained promises in angular js with $q service and it's working fine except the information of progressCallback ? let me draw what I have done so far?
calling function from my controller in below chainable promise way
fun1()
.then(resp1){
return fun2(resp1.id);
})
.then(resp2){
return $q.all([fun3(resp2.id),fun4(resp2.name)]);
})
.then(function(resp34){
return fun5();
})
.then(success)
.catch(errorhandler)
.finally(final);
and here is my all functions signature in service
var funX = function(param) {
var d = $q.defer();
d.notify('start with funX'); // Note: it was not working so placed inside else
doSomethingASync(param, function(err,data) {
if(err) { d.reject(err);}
else { d.notify('done with funX'); d.resolve(data); }
});
return d.promise;
});
Now my question is where do I receive this d.notify() message in my controller?
trial 1
.then(resp1, info1){
return fun2(resp1.id);
});
but it's undefined
trial 2
.then(resp1, err1, info1) {
return fun2(resp1.id);
}
but still undefined?
UPDATE
I have find a way by adding second parameter in finally()
.then().catch().finally(final, notify);
and here is my function definitions.
var errorHandler = function(err) {
console.error('Error returned from function:', err);
};
var final = function() {
console.log('Called Finally');
};
var notify = function(notification) {
console.log('Notify', notification);
};
var success = function(data) {
console.log('Success data');
console.log(data);
};
Can we get each promise function notification or this is not feasible?
But Now my query changed to
How do we add a .notify for the $q.all() ?
as I understand that $q.all returns a single promise which contains all promise resolve data;

Related

Why won't promise resolve in a service?

I have a factory NewEditSvc where I'm trying to share functionality common to NewCtrl and EditCtrl. One thing I'm trying to do is get feeParameters from the server inside the service but this is not working.
angular.module('feeSuitesApp')
.factory('NewEditSvc', function($q, FeeTypesSvc){
var _feeParameters = {};
function getParameters(){
return _feeParameters;
}
function setParameters(feeType){
var promise = $q(function(resolve, reject){
FeeTypesSvc.resource.get({id: feeType.id, association: 'fee_parameters'}).
$promise.then(function(data){
resolve(data);
}, function(error){
reject(error);
})
});
promise.then(function(data){
_feeParameters = data.master;
});
}
return {
getParameters: getParameters,
setParameters: setParameters
}
});
angular.module('feeSuitesApp')
.controller('NewCtrl', function(NewEditSvc, feeType){
$scope.feeParameters = NewEditSvc.getParameters();
$scope.feeType = feeType;
$scope.setParameters = function(feeType){
NewEditSvc.setParameters(feeType);
};
$scope.setParameters($scope.feeType);
});
After my last line $scope.feeParameters is not set. But if I return the promise variable from NewEditSvc.setParameters() and then in my NewCtrl I do the following:
var promise = $scope.setParameters($scope.feeType);
promise.then(function(data){
$scope.feeParameters = data.master;
});
Then everything works. What am I doing wrong?
The problem is, that you're not returning your promise in setParameters. https://docs.angularjs.org/api/ng/service/$q/
Just add return promise.

How to mock an angular $http call and return a promise object that behaves like $http

Is there a way to return an HttpPromise (or something similar) to mimic a call to $http? I want to set a global variable that indicates whether the real HTTP request is made or whether a fake HttpPromise object is returned with fake data.
For example, I have a service that is similar to this:
angular
.module('myservice')
.factory('MyService', ['$http', function($http) {
return {
get : function(itemId) {
if (isInTestingMode) {
// return a promise obj that returns success and fake data
}
return $http.get("/myapp/items/" + itemId);
}
};
} ]);
And in my controller, I have a call to the aforementioned service that looks similar to this:
// Somewhere in my controller
MyService.get($scope.itemId)
.success(function(data) {
$scope.item = data;
})
.error(function(data, status, headers, config) {
$scope.notFound = true;
});
I'm trying to not change the controller code; I want the success and error chaining to still work when in my "isInTestMode".
Is it possible to fake an HttpPromise in the way that I described in the service?
Below is a revised edition of the "MyService" above (a snippet) containing a success and error on the promise object. But, how do I execute the success method?
return {
get : function(itemId) {
if (isInTestingMode) {
var promise = $.defer().promise;
// Mimicking $http.get's success
promise.success = function(fn) {
promise.then(function() {
fn({ itemId : "123", name : "ItemName"}, 200, {}, {});
});
return promise;
};
// Mimicking $http.get's error
promise.error = function(fn) {
promise.then(null, function(response) {
fn("Error", 404, {}, {});
});
return promise;
};
return promise;
}
return $http.get("/myapp/items/" + itemId);
}
}
Just use the deferred method of the $qservice
var fakeHttpCall = function(isSuccessful) {
var deferred = $q.defer()
if (isSuccessful === true) {
deferred.resolve("Successfully resolved the fake $http call")
}
else {
deferred.reject("Oh no! Something went terribly wrong in your fake $http call")
}
return deferred.promise
}
And then you can call your function like an $http promise (you have to customize whatever you want to put inside of it, of course).
fakeHttpCall(true).then(
function (data) {
// success callback
console.log(data)
},
function (err) {
// error callback
console.log(err)
})
I found that this post is similar to what I was asking.
However, I wanted a way to mock my service call so that fake data could be returned instead of issuing a true HTTP request call. The best way to handle this situation, for me, is to use angular's $httpBackend service. For example, to bypass a GET request to my "items" resource BUT to not bypass GETs of my partials/templates I would do something like this:
angular
.module('myApp', ['ngMockE2E'])
.run(['$httpBackend', function($httpBackend) {
$httpBackend
.whenGET(/^partials\/.+/)
.passThrough();
$httpBackend
.whenGET(/^\/myapp\/items\/.+/)
.respond({itemId : "123", name : "ItemName"});
}]);
See this documentation for more information on $httpBackend.
I finally found a way using jasmin. $httpBackend was no option for me, as there were also non-$http-methods I needed mock on the same service. I also think that the controller test needing to specify the url is not perfect as imho the controller and its test should not need to know about it.
Here is how it works:
beforeEach(inject(function ($controller, $rootScope, $q) {
scope = $rootScope.$new();
mockSvc = {
someFn: function () {
},
someHttpFn: function () {
}
};
// use jasmin to fake $http promise response
spyOn(mockSvc, 'someHttpFn').and.callFake(function () {
return {
success: function (callback) {
callback({
// some fake response
});
},
then: function(callback) {
callback({
// some fake response, you probably would want that to be
// the same as for success
});
},
error: function(callback){
callback({
// some fake response
});
}
}
});
MyCtrl = $controller('MyCtrl', {
$scope: scope,
MyActualSvc: mockSvc
});
}));
You can implement your FakeHttp class:
var FakeHttp = function (promise) {
this.promise = promise;
this.onSuccess = function(){};
this.onError = function(){};
this.premise.then(this.onSuccess, this.onError);
};
FakeHttp.prototype.success = function (callback) {
this.onSuccess = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSucess, this.onError);
return this;
};
FakeHttp.prototype.error = function (callback) {
this.onError = callback;
/**You need this to avoid calling previous tasks**/
this.promise.$$state.pending = null;
this.promise.then(this.onSuccess, this.onError);
return this;
};
Then in your code, you would return a new fakeHttp out of the promise.
if(testingMode){
return new FakeHttp(promise);
};
The promise must be asynchronous, otherwise it won't work. For that you can use $timeout.
easy peasy!
You can do it using angular-mocks-async like so:
var app = ng.module( 'mockApp', [
'ngMockE2E',
'ngMockE2EAsync'
]);
app.run( [ '$httpBackend', '$q', function( $httpBackend, $q ) {
$httpBackend.whenAsync(
'GET',
new RegExp( 'http://api.example.com/user/.+$' )
).respond( function( method, url, data, config ) {
var re = /.*\/user\/(\w+)/;
var userId = parseInt(url.replace(re, '$1'), 10);
var response = $q.defer();
setTimeout( function() {
var data = {
userId: userId
};
response.resolve( [ 200, "mock response", data ] );
}, 1000 );
return response.promise;
});
}]);

Angular: Rewriting function to use promise

I'm using an Angular factory that retrieves data from a feed and does some data manipulation on it.
I'd like to block my app from rendering the first view until this data preparation is done. My understanding is that I need to use promises for this, and then in a controller use .then to call functions that can be run as soon as the promise resolves.
From looking at examples I'm finding it very difficult to implement a promise in my factory. Specifically I'm not sure where to put the defers and resolves. Could anyone weigh in on what would be the best way to implement one?
Here is my working factory without promise:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $state, StorageHandler) {
var obj = {
InitData : function() {
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
},
};
return obj;
});
Here's what I tried from looking at examples:
angular.module('MyApp.DataHandler', []) // So Modular, much name
.factory('DataHandler', function ($rootScope, $q, $state, StorageHandler) {
var obj = {
InitData : function() {
var d = $q.defer(); // Set defer
StorageHandler.defaultConfig = {clientName:'test_feed'};
StorageHandler.prepData = function(data) {
var i = 0;
var maps = StorageHandler.dataMap;
i = data.line_up.length;
while(i--) {
// Do loads of string manipulations here
}
return data;
}
// Check for localdata
if(typeof StorageHandler.handle('localdata.favorites') == 'undefined') {
StorageHandler.handle('localdata.favorites',[]);
}
return d.promise; // Return promise
},
};
return obj;
});
But nothing is shown in console when I use this in my controller:
DataHandler.InitData()
.then(function () {
// Successful
console.log('success');
},
function () {
// failure
console.log('failure');
})
.then(function () {
// Like a Finally Clause
console.log('done');
});
Any thoughts?
Like Florian mentioned. Your asynchronous call is not obvious in the code you've shown.
Here is the gist of what you want:
angular.module("myApp",[]).factory("myFactory",function($http,$q){
return {
//$http.get returns a promise.
//which is latched onto and chained in the controller
initData: function(){
return $http.get("myurl").then(function(response){
var data = response.data;
//Do All your things...
return data;
},function(err){
//do stuff with the error..
return $q.reject(err);
//OR throw err;
//as mentioned below returning a new rejected promise is a slight anti-pattern,
//However, a practical use case could be that it would suppress logging,
//and allow specific throw/logging control where the service is implemented (controller)
});
}
}
}).controller("myCtrl",function(myFactory,$scope){
myFactory.initData().then(function(data){
$scope.myData = data;
},function(err){
//error loudly
$scope.error = err.message
})['finally'](function(){
//done.
});
});

AngularJS: $q wait for all even when 1 rejected

I've been trying to wait for a couple of promises with Angular's $q but there seems to be no option to 'wait for all even when a promis is rejected'.
I've created an example (http://jsfiddle.net/Zenuka/pHEf9/21/) and I want a function to be executed when all promises are resolved/rejected, is that possible?
Something like:
$q.whenAllComplete(promises, function() {....})
EDIT: In the example you see that the second service fails and immediately after that the function in $q.all().then(..., function(){...}) is being executed. I want to wait for the fifth promise to be completed.
Ok, I've implemeted a basic version myself (I only want to wait for an array of promises). Anyone can extend this or create a cleaner version if they want to :-)
Check the jsfiddle to see it in action: http://jsfiddle.net/Zenuka/pHEf9/
angular.module('test').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
// Extention for q
$q.allSettled = $q.allSettled || function (promises) {
var deferred = $q.defer();
if (angular.isArray(promises)) {
var states = [];
var results = [];
var didAPromiseFail = false;
if (promises.length === 0) {
deferred.resolve(results);
return deferred.promise;
}
// First create an array for all promises with their state
angular.forEach(promises, function (promise, key) {
states[key] = false;
});
// Helper to check if all states are finished
var checkStates = function (states, results, deferred, failed) {
var allFinished = true;
angular.forEach(states, function (state, key) {
if (!state) {
allFinished = false;
}
});
if (allFinished) {
if (failed) {
deferred.reject(results);
} else {
deferred.resolve(results);
}
}
}
// Loop through the promises
// a second loop to be sure that checkStates is called when all states are set to false first
angular.forEach(promises, function (promise, key) {
$q.when(promise).then(function (result) {
states[key] = true;
results[key] = result;
checkStates(states, results, deferred, didAPromiseFail);
}, function (reason) {
states[key] = true;
results[key] = reason;
didAPromiseFail = true;
checkStates(states, results, deferred, didAPromiseFail);
});
});
} else {
throw 'allSettled can only handle an array of promises (for now)';
}
return deferred.promise;
};
return $q;
}]);
}]);
Analogous to how all() returns an array/hash of the resolved values, the allSettled() function from Kris Kowal's Q returns a collection of objects that look either like:
{ state: 'fulfilled', value: <resolved value> }
or:
{ state: 'rejected', reason: <rejection error> }
As this behavior is rather handy, I've ported the function to Angular.js's $q:
angular.module('your-module').config(['$provide', function ($provide) {
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function allSettled(promises) {
// Implementation of allSettled function from Kris Kowal's Q:
// https://github.com/kriskowal/q/wiki/API-Reference#promiseallsettled
var wrapped = angular.isArray(promises) ? [] : {};
angular.forEach(promises, function(promise, key) {
if (!wrapped.hasOwnProperty(key)) {
wrapped[key] = wrap(promise);
}
});
return $q.all(wrapped);
function wrap(promise) {
return $q.when(promise)
.then(function (value) {
return { state: 'fulfilled', value: value };
}, function (reason) {
return { state: 'rejected', reason: reason };
});
}
};
return $q;
}]);
}]);
Credit goes to:
Zenuka for the decorator code
Benjamin Gruenbaum for pointing me in the right direction
The all implementation from Angular.js source
The promise API in angularJS is based on https://github.com/kriskowal/q. I looked at API that Q provides and it had a method allSettled, but this method has not been exposed over the port that AngularJS uses. This is form the documentation
The all function returns a promise for an array of values. When this
promise is fulfilled, the array contains the fulfillment values of the
original promises, in the same order as those promises. If one of the
given promises is rejected, the returned promise is immediately
rejected, not waiting for the rest of the batch. If you want to wait
for all of the promises to either be fulfilled or rejected, you can
use allSettled.
I solved this same issue recently. This was the problem:
I had an array of promises to handle, promises
I wanted to get all the results, resolve or reject
I wanted the promises to run in parallel
This was how I solved the problem:
promises = promises.map(
promise => promise.catch(() => null)
);
$q.all(promises, results => {
// code to handle results
});
It's not a general fix, but it is simple and and easy to follow. Of course if any of your promises could resolve to null then you can't distinguish between that a rejection, but it works in many cases and you can always modify the catch function to work with the particular problem you're solving.
Thanks for the inspiration Zenuka, you can find my version at https://gist.github.com/JGarrido/8100714
Here it is, in it's current state:
.config( function($provide) {
$provide.decorator("$q", ["$delegate", function($delegate) {
var $q = $delegate;
$q.allComplete = function(promises) {
if(!angular.isArray(promises)) {
throw Error("$q.allComplete only accepts an array.");
}
var deferred = $q.defer();
var passed = 0;
var failed = 0;
var responses = [];
angular.forEach(promises, function(promise, index) {
promise
.then( function(result) {
console.info('done', result);
passed++;
responses.push(result);
})
.catch( function(result) {
console.error('err', result);
failed++;
responses.push(result);
})
.finally( function() {
if((passed + failed) == promises.length) {
console.log("COMPLETE: " + "passed = " + passed + ", failed = " + failed);
if(failed > 0) {
deferred.reject(responses);
} else {
deferred.resolve(responses);
}
}
})
;
});
return deferred.promise;
};
return $q;
}]);
})
A simpler approach to solving this problem.
$provide.decorator('$q', ['$delegate', function ($delegate) {
var $q = $delegate;
$q.allSettled = $q.allSettled || function (promises) {
var toSettle = [];
if (angular.isArray(promises)) {
angular.forEach(promises, function (promise, key) {
var dfd = $q.defer();
promise.then(dfd.resolve, dfd.resolve);
toSettle.push(dfd.promise);
});
}
return $q.all(toSettle);
};
return $q;
}]);
A simple solution would be to use catch() to handle any errors and stop rejections from propagating. You could do this by either not returning a value from catch() or by resolving using the error response and then handling errors in all(). This way $q.all() will always be executed. I've updated the fiddle with a very simple example: http://jsfiddle.net/pHEf9/125/
...
function handleError(response) {
console.log('Handle error');
}
// Create 5 promises
var promises = [];
var names = [];
for (var i = 1; i <= 5; i++) {
var willSucceed = true;
if (i == 2) willSucceed = false;
promises.push(
createPromise('Promise' + i, i, willSucceed).catch(handleError));
}
...
Be aware that if you don't return a value from within catch(), the array of resolved promises passed to all() will contain undefined for those errored elements.
just use finally
$q.all(tasks).finally(function() {
// do stuff
});

AngularJS : chaining http promises $q in a service

i have problems when it comes to $http promises in angularjs. i am doing this in my service: (the getSomething function should chain two promises)
the second function uses a external callback function!
app.service('blubb', function($http, $q) {
var self = this;
this.getSomething = function(uri, data) {
return self.getData(uri).then(function(data2) {
return self.compactData(uri, data2);
});
};
this.getData = function(uri) {
var deferred = $q.defer();
$http.get(uri).success(function(data) {
deferred.resolve(data);
}).error(function() {
deferred.reject();
});
return deferred.promise;
};
this.compactData = function(uri, data) {
var deferred = $q.defer();
/* callback function */
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
console.log(err);
deferred.reject(err);
}
/* end of function */
return deferred.promise;
};
});
when i use the service in my controller it doesn't output the console.log:
blubb.getSomething(uri, input).then(function(data) {
console.log(data)
});
edit:
if i define the callback function by myself in 'compactData' it works, but i am using "jsonld.compact" from https://raw.github.com/digitalbazaar/jsonld.js/master/js/jsonld.js and THIS doesn't work!
jsonld.compact(input, context, function(err, compacted) {
if(!err) {
console.log(compacted);
deferred.resolve(compacted);
} else {
deferred.reject('JSON-LD compacting');
}
});
i am getting the console.log output in jsonld.compact but the resolve doesn't work and i don't know why..
it only works with $rootScope.$apply(deferred.resolve(compacted));
I'm using chaining promises like this:
$http.get('urlToGo')
.then(function(result1) {
console.log(result1.data);
return $http.get('urlToGo');
}).then(function(result2) {
console.log(result2.data);
return $http.get('urlToGo');
}).then(function(result3) {
console.log(result3.data);
});
Chaining promises works here : jsfiddle
In your implementation, if $http.get or compactData goes wrong your console.log(data) will not be call.
You should maybe catch errors :
blubb.getSomething(uri, input).then(function(data) {
console.log(data);
}, function(err) {
console.log("err: " + err);
});
Whenever you use an external (external to AngularJS) callback that runs in a new turn/tick, you have to call $apply() on the appropriate scope after it has been invoked. This lets AngularJS know it has to update. You'll probably want to make sure you're only calling it once -- after all of the promises have been resolved. As an aside, jsonld.js provides a promises/future API, so if you're already using promises, you don't have to do that wrapper code above. Instead you can do:
var promisesApi = jsonld.promises();
var promise = promisesApi.compact(input, context);
// do something with the promise
I would suggest you to use a Factory instead of a service.
Just return the function from the factory and use it in your controller

Resources