I have this code:
var geocode = function(value) {
var request;
.....
var dResult = Q.defer();
geocoder.geocode(request, function (results) {
dResult.resolve(results);
});
return dResult.promise;
};
var cancelWatch;
$scope.$watch('value', function (value) {
$timeout.cancel(update);
update = $timeout(function () {
$scope.geocodedResult = geocode(value);
}, 300);
});
in line 15 $scope.geocodedResult is a promise that sooner or later will become the result value and the scope should refresh. This unfortunately does not happen.
The code works if I do
geocode(value).then(function(result) {
$scope.geocodedResult = result;
$scope.$digest();
});
What am I doing wrong?
UPDATE:
I'm now trying to use only $q but I cannot get it to work:
this.getCurrentPosition = function () {
var dCurrentPosition = $q.defer();
if (currentPosition) {
dCurrentPosition.resolve(currentPosition);
} else {
navigator.geolocation.getCurrentPosition(function (cp) {
currentPosition = cp;
dCurrentPosition.resolve(currentPosition);
});
}
return dCurrentPosition.promise;
};
this.getCurrentLoc = function () {
return self.getCurrentPosition().then(function (currentPosition) {
return [currentPosition.coords.longitude, currentPosition.coords.latitude];
});
};
a breakpoint in
return [currentPosition.coords.longitude, currentPosition.coords.latitude];
will never get triggered while it works fine with Q
If you use Angular's $q instead of Q, it should work:
var dResult = $q.defer();
$q promises are recognized by the templating engine in angular, which means that in templates you can treat promises attached to a scope as if they were the resulting values. -- $q docs
So $scope.geocodedResult can be set to the $q promise, and when the promise is resolved, the scope property will automatically update.
Related
how to get the resolved value directly in angular promise?
I want to get the string "ok" in below function, not the promise object.
anybody help??
var getReturn = function() {
var defer = $q.defer();
var promise = defer.promise;
defer.resolve("ok");
return promise.then(function (value) {
console.log(value);
return(value);
});
};
Since the function is not asynchronous, there's no need for a promise. So the correct way doing so will be:
var getReturn = function() {
return "ok";
}
If you still want this as a promise you should do as follows:
var getReturn = function() {
var defer = $q.defer();
setTimeout(function() {
defer.resolve("ok");
},0)
return defer.promise;
};
And wherever you call the function do the then:
getReturn().then(function (value) {
console.log(value);
});
You can't actually do that when you are dealing with promises. Promises are meant to be awaited or deferred until the expected value is ready meaning you should get the value only in it's "then" block especially if it's an $http promise.
I have a Service that expose a function that receives a parsed CSV (using papaparse) and promise that reflects the parsing status:
If the file was missing mandatory fields, the promise is rejected
Otherwise, It parses each row into an item and auto populates the missing fields (the auto population process is asynchronous).
when all items are populated, the function resolves the promise with the items array
The function I want to test is onCsvParse:
angular.module('csvParser', [])
.factory('csvParser', ['$http',
function($http) {
var service = {
onCsvParse: function(results, creatingBulkItems) {
var errors = this.getCsvErrors(results);
if (errors.length > 0) {
//reject
creatingBulkItems.reject(errors);
} else {
var items = this.parseCsv(results);
var autoPopulateItems = [],
populatedItems = [];
for (var i = 0; i < populatedItems.length; i++) {
var item = items[i];
if (item.name === "" /*or some any field is missing */ ) {
// auto populate item
autoPopulateItems.push(this.autoPopulateItem(item));
} else {
var populatedItem = $q.when(item);
populatedItems.push(populatedItem);
}
}
populatedItems =autoPopulateItems.concat(populatedItems);
var populatingAllItems = $q.all(populatedItems);
populatingAllItems.then(function(items) {
creatingBulkItems.resolve(items);
}, function(err) {
creatingBulkItems.resolve(err);
});
}
},
autoPopulateItem: function(newItem) {
var populatingItem = $q.defer();
var item = angular.copy(newItem);
$http.post('api/getItemData', { /*.....*/ })
.success(function(response) {
//----Populate item fields
item.name = response.name;
//....
//resolve the promise
populatingItem.resolve(item)
}).error(err) {
// resolving on error for $q.all indication
populatingItem.resolve(item)
};
return populatingItem.promise;
}
}
return service;
}
])
My test for this method looks as follows (simplified):
describe('bulk items upload test', function() {
//upload csv & test scenarios...
var $rootScope, $q, csvResults = {};
var $httpBackend, requestHandler;
beforeEach(module('csvParser'));
beforeEach(inject(function(_$rootScope_, _$q_) {
$rootScope = _$rootScope_;
$q = _$q_;
}));
beforeEach(inject(function($injector) {
// Set up the mock http service responses
$httpBackend = $injector.get('$httpBackend');
// backend definition common for all tests
requestHandler = $httpBackend.when('POST', 'api/getItemData')
.respond({
name: "name",
description: "description",
imageUrl: "www.google.com"
});
// afterEach(function(){ $rootScope.$apply();});
}));
it('Should parse csv string', function(done) {
var csvString = "Name,Description of the page";//...
Papa.parse(csvString, {
complete: function(results) {
csvResults = results;
done();
}
});
});
it('Should fail', function(done) {
var creatingBulkItems = $q.defer();
console.log("here..");
csvParser.onCsvParse(csvResults, creatingBulkItems);
creatingBulkItems.promise.then(function() {
console.log("1here..");
//promise is never resolved
expect(1).toEqual(1);
done();
}, function() {
//promise is never rejeceted
console.log("2here..");
expect(1).toEqual(1);
done();
});
$rootScope.$apply();
});
});
With this I get the error: Error: Timeout - Async callback was not invoked within timeout specified by jasmine.DEFAULT_TIMEOUT_INTERVAL.
the promises are not resolved, although I called $rootScope.$apply() and I am also not calling a real asynchronous call (only mocks,except $q.all).
How can I make it work?
Invalid syntax. You need to pass a function to the error callback.
}).error(function(err) {
// resolving on error for $q.all indication
populatingItem.resolve(item)
});
return populatingItem.promise;
Also you jasime test require some more initialization:
http://plnkr.co/edit/wjykvpwtRA0kBBh3LcX3?p=preview
After reading this article: https://gist.github.com/domenic/3889970 I found out my problem.
The key point is to flatten the promise chains using the promise.then return value.
This [promise.then] function should return a new promise that is fulfilled when the given fulfilledHandler or errorHandler callback is finished. This allows promise operations to be chained together. The value returned from the callback handler is the fulfillment value for the returned promise. If the callback throws an error, the returned promise will be moved to failed state.
Instead of resolving the outer promise within the inner promise success/fail callbacks, the outer promise is resolved in the inner promise.then callbacks
So my fix is something like this:
onCsvParse: function(results) {
var errors = this.getCsvErrors(results);
if (errors.length > 0) {
var deferred = $q.defer();
//reject
return deferred.reject(errors);
} else {
var items = this.parseCsv(results);
var autoPopulateItems = [],
populatedItems = [];
for (var i = 0; i < populatedItems.length; i++) {
var item = items[i];
if (item.name === "" /*or some any field is missing */ ) {
// auto populate item
autoPopulateItems.push(this.autoPopulateItem(item));
} else {
var populatedItem = $q.when(item);
populatedItems.push(populatedItem);
}
}
populatedItems = autoPopulateItems.concat(populatedItems);
var populatingAllItems = $q.all(populatedItems);
return populatingAllItems.then(function(items) {
return items;
}, function(err) {
return err;
});
}
},
test code:
it('Should not fail :)', function(done) {
csvParser.onCsvParse(csvResults).then(function(items) {
//promise is resolved
expect(items).not.toBeNull();
done();
}, function() {
//promise is rejeceted
//expect(1).toEqual(1);
done();
});
$rootScope.$apply();});
One of my promises during feeds.forEach cycle ends up with error. Maybe because this in LoadData never executes line $rootScope.links = urls; which is inside then. How to fix it?
app.service('LoadData', ['FeedService', 'EntryStateUrlService', '$q',
function(FeedService, EntryStateUrlService, $q) {
this.loadData = function(feedSrc) {
FeedService.parseFeed(feedSrc).then(EntryStateUrlService.getEntryStateUrl).then(function(data) {
console.log(data);
$rootScope.links = data;
});
}
}
]);
app.service('FeedService', function($http, $q) {
this.parseFeed = function(url) {
var deferred = $q.defer();
$http.jsonp('//ajax.googleapis.com/ajax/services/feed/load?v=1.0&callback=JSON_CALLBACK&q=' + encodeURIComponent(url))
.success(function(res) {
deferred.resolve(res.data.responseData.feed.entries);
}).error(function() {
deferred.reject();
});
return deferred.promise;
}
});
app.service('EntryStateUrlService', ['$state', '$q',
function($state, $q) {
this.getEntryStateUrl = function(feeds) {
var deferred = $q.defer();
var idx = 0,
promises = [],
promise = null;
feeds.forEach(function(e) {
promise = $http.jsonp(e.link).success(function(data) {
/*stuff*/
e['entryStateUrl'] = data.link; // UPDATED
deferred.resolve(data);
});
promises.push(promise);
}); //forEach
return $q.all(promises);
}
}
]);
UPDATE
I don't really understand how $q.all as a big promise object composite of many other promises will deliver data to service LoadData....
UPDATE2
It seems like since one promise fails (because the one of the urls is invalid) $q.all fails and never makes to then(). How to go around that? I need to get all data from all successful promises.
One thing is you'll need to inject $rootScope in order to use it:
app.service('LoadData', ['FeedService', 'EntryStateUrlService', '$q', '$rootScope',
function(FeedService, EntryStateUrlService, $q, $rootScope) {
//...
}
]);
Also in the Q library there's a method called allSettled that will allow you to view all the promise results even if one fails. The angular $q does not have this method, but several people have found it useful so they've created extensions for it. Here's an example of one on a Github Gist:
angular.module('qAllSettled', []).config(function($provide) {
$provide.decorator('$q', function($delegate) {
var $q = $delegate;
$q.allSettled = function(promises) {
return $q.all(promises.map(function(promise) {
return promise.then(function(value) {
return { state: 'fulfilled', value: value };
}, function(reason) {
return { state: 'rejected', reason: reason };
});
}));
};
return $q;
});
});
Using this method will return all results, whether rejected or not. The returned object will have a state property that will tell you the status of the promise:
return $q.allSettled(promises);
Then you can check the status of each promise and add it to the links as needed:
FeedService.parseFeed(feedSrc)
.then(EntryStateUrlService.getEntryStateUrl)
.then(function(results) {
$scope.links = [];
results.forEach(function (result) {
if (result.state === "fulfilled") {
$scope.links.push(result.value);
} else {
var reason = result.reason;
}
});
});
Plunker Demonstration
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.
});
});
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
});