So I have a simple example of using $q.all to batch $resource calls, what I want to know is why is my update handler never called?
I would have thought it would be called after each promise is successfully completed?
Only the result handler is called. What am I doing wrong?
Here is the code snippet:
var promises = [];
angular.forEach($scope.mappedData, function(item) {
var resource = new Resource(item);
promises.push(resource.$save());
});
$q.all(promises).then(
function(result) {
console.log('result', result);
},
function(error) {
console.log('error', error);
},
function(notify) {
console.log('notify', notify);
}
);
$q.all creates a new singular promise that once all the previous promises are complete will then continue on. If you want to do each one individually you'll have to reference them individually.
I had the same problem, and I came with this solution. I've tried to arrange the code for your case.
var results = [], errors = [], d = $q.defer()
angular.forEach($scope.mappedData, function(item) {
var resource = new Resource(item);
resource.$save().promise
.then(function (result) {
results.push(result)
if(results.length == $scope.mappedData.length)
d.resolve(results)
}, function (err) {
errors.push(err)
results.push(null) // Very important =P
}, function (notf) {
d.notify(notf)
})
})
d.promise.then(function (results) {
console.log(results)
}, function (err) {
console.error(err)
}, function (notf) {
console.info(notf)
})
Let me know if it helps ;)
Related
I've currently got an endpoint that relies on a JSON body in order for deletion to happen. This is the following code:
if (toDeleteValue.length > 0) {
var deleteRequest = [];
for (var i = 0; i < toDeleteValue.length; i++) {
var service = {};
service.serviceId = $scope.siteServices[toDeleteService[i]].serviceId;
toDeleteValue.push(service);
}
var deleteUrl = "api/class/" + $scope.targetEntity.serviceId+ "/student";
await asyncDeleteUrl(deleteUrl, deleteRequest);
}
async function asyncDeleteUrl(deleteUrl, toBeDeleted) {
return new Promise(function (resolve, reject) {
$http.delete(deleteUrl, toBeDeleted)
.then(function (response) {
resolve(response);
},
function (errorResponse) {
reject(errorResponse);
$scope.statusDialog('Bad Modification Interrupted', errorResponse);
});
});
}
I keep getting an error saying the required rest body is missing but I'm not sure why that would be the case. Any help would be appreciated, thank you.
The second argument of the $http.delete method is a config object. Send data using the data property of that object:
function asyncDeleteUrl(deleteUrl, toBeDeleted) {
var config = { data: toBeDeleted };
return $http.delete(deleteUrl, config)
.catch(function (errorResponse) {
$scope.statusDialog('Bad Modification Interrupted', errorResponse);
throw errorResponse;
});
}
For more information, see
AngularJS $http Service API Reference - delete
Promise in ForEach
I'm having a problem, I need to call a service N times and I've tried this:
This is my function that calls the service, I send a parameter that is "code" and returns a promise.
var get222 = function(codigo) {
var defer = $q.defer();
var cbOk = function(response) {
//console.log(response);
defer.resolve(response);
}
var cbError = function(error) {
//console.log(error);
defer.reject(error);
}
VentafijaAccessService.getProductOfferingPrice(codigo, cbOk, cbError);
return defer.promise;
}
After this function, I get the codes and I need to make a call N times and when they finish returning the promise to get the answer for each code I send.
var getProductOfferingPrice = function(_aCodigoOfertas) {
var deferred = $q.defer();
var results = [];
var promises = [];
angular.forEach(_aCodigoOfertas, function(codigo) {
promises.push(get222(codigo));
});
$q.all(promises)
.then(function(results) {
// here you should have all your Individual Object list in `results`
deferred.resolve({
objects: results
});
});
return deferred.promise;
};
The calls to the services IF THEY ARE EXECUTED, but never returns the promise, I can not get the response of each one.
EDIT
VentaDataService.js
var get222 = function(codigo) {
return $q(function(resolve, reject) {
VentafijaAccessService.getProductOfferingPrice(codigo, resolve, reject);
});
}
var getProductOfferingPrice = function(_aCodigoOfertas) {
return $q.all(_aCodigoOfertas.map(function(codigo) {
return get222(codigo);
}));
};
VentaFijaController.js
var cbOk2 = function(response) {
console.log(response);
}
var cbError2 = function(error) {
console.log(error);
}
VentafijaDataService.getProductOfferingPrice(codigoOfertas)
.then(cbOk2, cbError2)
There's no need to wrap a new promise around this. Just return the $q.all() promise:
VentafijaAccessService.getProductOfferingPriceAllPromise = function(_aCodigoOfertas) {
var promises = [];
angular.forEach(_aCodigoOfertas, function(codigo) {
promises.push(get222(codigo));
});
return $q.all(promises);
};
The resolved value of the returned promise will be an array of results.
VentafijaAccessService.getProductOfferingPriceAllPromise(...).then(results => {
console.log(results);
}).catch(err => {
console.log(err);
});
If _aCodigoOfertas is an array, you can further simply getProductOfferingPrice to this:
VentafijaAccessService.getProductOfferingPriceAllPromise = function(_aCodigoOfertas) {
return $q.all(_aCodigoOfertas.map(function(codigo) {
return get222(codigo);
}));
};
You can also vastly simplify get222() to this:
var get222 = function(codigo) {
return $q(function(resolve, reject)) {
// call original (non-promise) implementation
VentafijaAccessService.getProductOfferingPrice(codigo, resolve, reject);
});
}
Then, in the controller, you could do this:
VentafijaDataService.getProductOfferingPriceAllPromise(codigoOfertas).then(function(result) {
console.log(result);
}).catch(function(e) {
console.log('Error: ', e);
});
I am a bit confused about this. I have two get calls inside a function. Once this complete function, that is the two get calls are done, only then is this function done with its work. how should I used $q to get this to work as I want it? This is what I have now:
function updateBlackList() {
$http.get("http://127.0.0.1:8000/blacklist/entries/vehicle").then(function (res){
console.log(res)
}).catch(function (err) {
console.log(err)
});
})
$http.get("http://127.0.0.1:8000/blacklist/entries/person").then(function (res){
console.log(res)
}).catch(function (err) {
console.log(err)
});
});
return $q.when();
}
Here withint another function I need to wait for the above fiunction to complete:
BlackListService.updateBlackList().then(function() {
addVersion(server_version).then(function () {
console.log("Blacklist update complete")
})
})
Its not doing it like I was suspecting it to do. The Blacklist complete console is called before the tw get request are done
You want to combine both promises in one with $q.all()
function updateBlackList() {
return $q.all([
$http.get("http://127.0.0.1:8000/blacklist/entries/vehicle")
.then(function (res){console.log(res)})
.catch(function (err) {console.log(err)}),
$http.get("http://127.0.0.1:8000/blacklist/entries/person")
.then(function (res){console.log(res)})
.catch(function (err) {console.log(err)});
]);
}
Also, for your second example, you can chain promises to have a better looking code:
BlackListService.updateBlackList()
.then(function() {
return addVersion(server_version);
})
.then(function () {
console.log("Blacklist update complete");
})
Use $q.all.
var VEHICLE_URL = "http://127.0.0.1:8000/blacklist/entries/vehicle";
var PERSON_URL = "http://127.0.0.1:8000/blacklist/entries/person";
function updateBlackList() {
var p1 = $http.get(VEHICLE_URL).then(whatever);
var p2 = $http.get(PERSON_URL).then(whatever);
return $q.all([p1, p2]);
}
updateBlackList()
.then(whateverElse);
The past view days I read a lot of best practices in handling with promises. One central point of the most postings where something like this:
So if you are writing that word [deferred] in your code
[...], you are doing something wrong.1
During experimenting with the error handling I saw an for me unexpected behavior. When I chain the promises and It run into the first catch block the second promise gets resolved and not rejected.
Questions
Is this a normal behavior in other libs / standards (e.g. q, es6), too and a caught error counts as solved like in try / catch?
How to reject the promise in the catch block so that the second gets, called with the same error / response object?
Example
In this example you see 'I am here but It was an error'
Full Plunker
function BaseService($http, $q) {
this.$http = $http;
this.$q = $q;
}
BaseService.prototype.doRequest = function doRequest() {
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
})
.catch(function(response) {
// do some baisc stuff e.g. hide spinner
});
}
function ChildService($http, $q) {
this.$http = $http;
this.$q = $q;
}
ChildService.prototype = Object.create(BaseService.prototype);
ChildService.prototype.specialRequest = function specialRequest() {
return this.doRequest()
.then(function (response) {
alert('I am here but It was an error');
})
.catch(function (response) {
// do some more specific stuff here and
// provide e.g. error message
alert('I am here but It was an error');
return response;
});
}
Workaround:
With this workaround you can solve this problem, but you have to create a new defer.
BaseService.prototype.doRequest = function doRequest() {
var dfd = this.$q.defer();
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
dfd.resolve(response);
})
.catch(function(response) {
// do some basic stuff e.g. hide spinner
dfd.reject(error);
});
}
Your workaround is almost correct, you can simplify it to the following:
BaseService.prototype.doRequest = function doRequest() {
return this.$http({
method: 'GET',
url: 'not/exisint/url'
})
.then(function (response) {
// do some basic stuff
return response;
}, function (error) {
return this.$q.reject(error);
});
}
$q.reject is a shortcut to create a deferred that immediately get's rejected.
Yes, this is default behaviour in other libraries as well. .then or .catch simply wraps the return value into a new promise. You can return a rejected promise to make the .catch chain work.
You can also do the opposite, for instance when you want to reject the promise in the success callback for whatever reason:
function getData() {
return this.$http.get(endpoint).then(result => {
// when result is invalid for whatever reason
if (result === invalid) {
return this.$q.reject(result);
}
return result;
}, err => this.$q.reject(err));
}
getData().then(result => {
// skipped
}, error => {
// called
});
See example above
Just to add to Dieterg's answer and to your workaround, you can also wrap the code into $q constructor:
BaseService.prototype.doRequest = function doRequest() {
return $q(function (resolve, reject) {
$http.get('not/exisint/url').then(function (response) { // success
/* do stuff */
resolve(response);
}, function (error) { // failure
/* do stuff */
reject(error);
});
});
};
I'm totally new to ionic/angular, this is my code:
.controller('PostCtrl', function($scope, Posts, $cordovaSQLite, $http) {
$scope.getPosts = function() {
$http.get('http://localhost/postIds').then(function(resp) {
_.each(resp.data, function(id) {
var query = "SELECT id FROM posts WHERE id = ?";
$cordovaSQLite.execute(db, query, [id]).then(function(res) {
if(res.rows.length = 0) {
$http.get('http://localhost/post/' + id).then(function(resp) {
var post = resp.data;
var query = "INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)";
$cordovaSQLite.execute(db, query, [post.id, post.title, post.user, post.content]).then(function(res) {
// success
}, function(err) {
console.log(err);
});
}, function(err) {
console.log(err);
});
}
}, function (err) {
console.error(err);
});
});
}, function(err) {
console.log(err);
});
}
})
what am I doing is
get all ids from server
if id doesnt exist in db(sqlite)
get post by id from server
insert post into db
It ends up deeply nested, ugly.
what is the ionic, angular way to do this?
As the others suggested the best option is to use promises so you don't have to nest statements like you're doing.
AngularJs uses $q promises:
A service that helps you run functions asynchronously, and use their
return values (or exceptions) when they are done processing.
On the internet there are tons of articles about promises and how to chain them.
Recently I found this article which explains the common mistakes with promises.
It's worth reading cause it goes deep into the topic.
In AngularJs you would create a promise using the $q service:
function doSomething() {
var deferred = $q.defer();
deferred.resolve({value: true});
return deferred.promise;
}
This bit of code returns a promise which is resolved - since there's no async operation - when it's called. It would return an object with a property value = true.
The cool thing about promises is the fact that you can chain them:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
});
passing the result of the previous - resolved - promise.
If promises are rejected because of some exceptions:
deferred.reject({value: false});
you can trap the error and stop the execution in the chain:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
})
.catch(function(reason){
// reason for failure.
});
Finally you can use the finally to do some cleanup or other things:
doSomething()
.then(function(result){
// result.value should be true.
return doSomething();
})
.then(function(result){
// result.value should be true.
// this is the result of the second call.
})
.catch(function(reason){
// reason for failure.
})
.finally(function(){
// it's going to be executed at the end of the chain, even in case of error trapped by the catch.
});
Things are not so simple, though. At the beginning you might find yourself spending a few hours debugging the code.
How would I fix your code ?
First of all I would create a function which fetch the ids calling the web api:
function fetchIds() {
console.log('Fetching Ids ...');
var deferred = $q.defer();
$http({
method: 'GET',
url: 'http://localhost/postIds',
params: {}
})
.success(function(data) {
deferred.resolve(data);
})
.error(function(data, status) {
deferred.reject(data);
});
return deferred.promise;
}
As you can see I've implemented the system described above.
$http already returns a promise but I wrapped it creating a new promise, anyway.
Then I would have to query the database to find the non existing ids (I didn't put my code in a loop as it is easier to get all the records in one call):
function queryForIds(ids) {
console.log('Querying for Ids ' + ids.toString() + ' ...');
var deferred = $q.defer();
var params = [];
for (var i = 0; i < ids.length; i++) {
params.push('?');
}
window.myDatabase.transaction(function(tx) {
tx.executeSql("SELECT * FROM posts WHERE postId IN (" + params.join(',') + ")", ids,
function(tx, results) {
deferred.resolve(results.rows);
},
function(tx, reason) {
deferred.reject(reason);
});
});
return deferred.promise;
}
My code is going to be slightly different from your as I've used WebSql cause I wanted to test it in the browser.
Now we need to find the ids which do not exist in the db:
function getNonExistingIds(ids, dbData) {
console.log('Checking if Ids ' + ids.toString() + ' exist in the db ...');
if (!ids || ids.length === 0) {
console.log('No ids');
return [];
}
if (!dbData || dbData.length === 0) {
console.log('database is empty');
return ids;
}
var dbIds = [];
angular.forEach(dbData, function(data, key) {
dbIds.push(data.postId);
});
var nonExisting = [];
angular.forEach(ids, function(id, key) {
var found = $filter('filter')(dbIds, id, true);
if (found.length === 0) {
nonExisting.push(id);
}
});
return nonExisting;
}
This function does not return a promise but you still can pipe it like you would do with a real promise (You'll find out how later).
Now we need to call the web api to fetch the posts for the ids which couldn't be found in the database:
function fetchNonExisting(ids) {
if (!ids || ids.length === 0) {
console.log('No posts to fetch!');
return;
}
console.log('Fetching non existing posts by id: ' + ids.toString() + ' ...');
var promises = [];
angular.forEach(ids, function(id, key) {
var promise = $http({
method: 'GET',
url: 'http://localhost/post/' + id,
params: {}
});
promises.push(promise);
});
return $q.all(promises);
}
Things here get interesting.
Since I want this function to return one and only result with an array of posts I've created an array of promises.
The $http service already returns a promise. I push it in an array.
At the end I try to resolve the array of promises with $q.all. Really cool!
Now we need to write the posts fetched in the database.
function writePosts(posts) {
if (!posts || posts.length === 0)
{
console.log('No posts to write to database!');
return false;
}
console.log('Writing posts ...');
var promises = [];
angular.forEach(posts, function(post, key) {
promises.push(writePost(post.data));
});
return $q.all(promises);
}
Again, we are chaining an array of promises so that we can resolve them all in one go.
This function up here calls writePost:
function writePost(post) {
return $q(function(resolve, reject) {
window.myDatabase.transaction(function(tx) {
tx.executeSql("INSERT INTO posts (postId, title, user, content) VALUES (?,?,?,?)", [post.id, post.title, post.user, post.content],
function(tx, result) {
console.log('INSERT result: ' + result);
resolve(result);
},
function(tx, reason) {
console.log('INSERT failure: ' + reason);
reject(reason);
});
});
});
}
this bit here is quite complicated cause WebSql doesn't work with promises and I want them to be resolve in one go and get the result back.
Now what can you do with all these functions? Well, you can chain them as I explained earlier:
var ids = [];
fetchIds()
.then(function(data) {
console.log(data);
ids = data;
return queryForIds(data);
})
.then(function(dbData) {
return getNonExistingIds(ids, dbData);
})
.then(function(nonExistingIds) {
console.log('Non existing ids: ' + nonExistingIds);
return fetchNonExisting(nonExistingIds);
})
.then(function(response) {
return writePosts(response);
})
.then(function(result) {
console.log('final result: ' + result);
})
.catch(function(reason) {
console.log('pipe error: ' + reason);
})
.finally(function() {
// Always Executed.
});
The final result can find found in this gist.
If you prefer to download the whole application and test it on your PC, this is the link (myApp).