The following code returns an RSVP promise from each getJSON method:
getJSON('/login')
.then(getJSON('/errors').then(function(users) {
self.user = users;
}))
.then(getJSON('contacts').then(function(contacts) {
self.contacts = contacts;
}))
.then(getJSON('companies').then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
}));
My understanding of promises is obviously wrong, I don't want to provide an error callback for each then and instead I thought that the error would be forwarded on to the next available error callback in one of the subsequent then functions.
In the above code, the getJSON on line 2 will be rejected but it is not forwarded onto the error callback in the last then.
Do I have to provide an error callback for each then. This does not seem any different from callback hell.
Is there any reason you're not making all requests at once?
self.user = getJSON("login");
self.contacts = getJSON("contacts");
self.companies = getJSON("companies");
// not sure what "/errors" does or what you assign it to, add it if you please
//.hash will go through object properties and wait for all promises to resolve
return RSVP.hash(self).then(function(results){
//here, self now contains .user .contacts and .companies
callback(self); //DO NOT do this, instead return the promise
}).catch(function(err){
//this is where all error handling goes
});
Note the return there, it's best to return the promise rather than call a callback, you can .then it from the outside.
I think you're chaining your promises in a wrong way. Your getJSONS are not executing in succession after the previous completes.
When you call the first getJSON (getJSON('/login')), you are not passing two handlers to its then method. You are passing a new getJSON call. This means that, just after the call to getJSON finishes (but before the ajax call ends) you are executing the second getJSON (getJSON('/errors')). You are passing the resulting promise as the first argument of the then method of getJSON('/login'). By doing this, this promise will resolve or reject with the same value as getJSON('/errors'). Okay but...
.then(getJSON('companies').then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
}));
Here you are passing a new promise, the one returned by getJSON(companies) as the first argument of a then method. The promise to which this method belongs, when rejected, will try to call the function passed as the second argument ... but there's none! So, because getJSON('companies') does not reject, you don't receive an error. I've rewritten your chain:
getJSON('/login').then(
function(users) {
self.user = users;
return getJSON('/errors');
}).then(function() {
return getJSON('contacts')
}).then(function(contacts) {
self.contacts = contacts;
return getJSON('companies');
}).then(function(companies) {
self.companies = companies;
callback(self);
}, function(err){
console.log('does not get here')
});
Now I think it should work fine. After a succesfull resolve of each promise, a function making a new getJSON request will execute, and it will return its promise. If any of them rejects, the rejection will pass through until a then with second argument is found, which happens at the end of the chain. The last function(err) will capture anything that went wrong before that point.
Similar to Benjamin Gruenbaum's, but slightly more concise.
return RSVP.hash({
user: getJSON("login");
contacts: getJSON("contacts");
companies: getJSON("companies");
}}).then(callback).catch(function(err){
//this is where all error handling goes
});
Related
I am starting to learn about promises in Javascript and I am still not getting my head around it. The code below is mostly real code. I have placed several debugger statements so the program stops and I can understand how the flow works and inspect some variables. I have read some blog posts about promises and I still can't understand everything. This is from an app which uses AngularJS and q library.
Few questions:
1- What does deferred.Resolve() do exactly? What is it doing with response.data? When I inspected the 'deferred' object and its 'promise' object, I couldn't see any trace for response.data.
2- When I resumed execution after debugger #1, I thought the http post statement would run but execution jumped to the return statement. I guess that's where the promise jumped in and the post will happen in the future?
3- How do I know when the post will happen when the function returns? The caller will get the return promise, what is the caller expected to do with it?
this.GetData = function()
{
var data = blahblah;
var deferred = this.$q.defer();
debugger; //1
this.$http.post(someurl, data,
{
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
handleErrors: false
})
.then(function(response) {
debugger; //2
(domesomething...)
deferred.resolve(response.data);
},
function(error) {
(logerror...)
deferred.reject(error);
});
debugger; //3
return deferred.promise;
};
It's appropriate to use q.defer() (an explicit creation of a promise) when wrapping callback style code, "promisifying"...
this.timeoutWithAPromise = function(msec) {
let defer = q.defer();
setTimeout(() => defer.resolve(), msec);
return defer.promise;
};
The foregoing says: "Create a promise and return it right away. That promise will be fulfilled when msec has passed.
Question 1: resolve() fulfills the promise, calling whatever function that's been set with then(), passing it whatever parameter was passed to resolve.
Question 2: You're right, the async operation is begun and a promise for it's completion is returned right away.
Question 3a: The post will begin (or the timeout will commence in my e.g.) on another thread as soon as that invocation is made. It will continue concurrently with execution on this calling thread, until it completes, which you understand from Question 1, is done by resolving the promise, invoking it's then().
Question 3b: What is the caller to do with a returned promise? Attach a block to it that you wish to run upon completion, possibly additional async actions. Taking my example...
let self = this;
self.timeoutWithAPromise(1000).then(()=> {
console.log('1000msec have passed');
return self.timeoutWithAPromise(1000);
}).then(()=> {
console.log('now, another 1000msec have passed');
// ...
Rewriting your example, realizing that $http already conforms to promises...
var data = blahblah;
let headers = { 'Content-Type': 'application/x-www-form-urlencoded' };
let config = { headers: headers, handleErrors: false };
let httpPromise = this.$http.post(someurl, data, config).then((response)=> {
console.log('got a response. tell our caller about it');
return response.data;
}).catch((error)=>
console.log('got an error. either handle it here or throw it');
throw error;
});
// this is important: return the httpPromise so callers can chain off of it
return httpPromise;
Now a caller can say:
let promiseToDoEvenMore = this.GetData().then((data)=> {
console.log(data);
return this.GetMoreData(); // etc.
});
return promiseToDoEvenMore; // and so on
I have a method that returns a $q (Q) promise:
var subtypesMetadataResolved = restService.getNodeSubtypesMetadata();
Now, when metadata is available, I want to run two functions to process them. First, I thought to chain them like this:
subtypesMetadataResolved.then(createNodes).then(prepareDataForWidgets)
But then I realized that since they both require data that is returned by the subtypesMetadataResolved promise I would need to return this data also from createNodes success callback so that it's passed into prepareDataForWidgets, which is not an option. So then I made it like this:
subtypesMetadataResolved.then(createNodes)
subtypesMetadataResolved.then(prepareDataForWidgets)
Which works like I need. But now the problem is how do I get my rejection callback called when subtypesMetadataResolved is rejected and don't get neither createNodes nor prepareDataForWidgets callbacks triggered in that case? I have the following, but after triggering nodeSubtypesMetadataErrorCb it also triggers createNodes callback:
subtypesMetadataResolved.catch(nodeSubtypesMetadataErrorCb);
subtypesMetadataResolved.then(createNodes)
subtypesMetadataResolved.then(prepareDataForWidgets)
Here is how I reject subtypesMetadataResolved:
EDIT:
function getNodeSubtypesMetadata(subtype) {
return $q.when("success!").then(function(){
debugger
throw new Error();
});
}
var subtypesMetadataResolved = getNodeSubtypesMetadata();
subtypesMetadataResolved.then(successCb1);
subtypesMetadataResolved.then(successCb2);
subtypesMetadataResolved.catch(nodeSubtypesMetadataErrorCb);
$q.all([
typesMetadataResolved,
subtypesMetadataResolved
]).then(init);
The problem is that you are assigning a promise with the handled error to subtypesMetadataResolved. If you call .then() on this, it will call the then() callbacks because the .catch() callback is essentially "handling" the error.
To solve this, assign the unhandled promise to your variable, and then call .catch()/.then() on that:
var subtypesMetadataResolved = getNodeSubtypesMetadata();
subtypesMetadataResolved.catch(nodeSubtypesMetadataErrorCb);
subtypesMetadataResolved.then(function(){
debugger
});
subtypesMetadataResolved.then(function () {
debugger
});
As a matter of style, I would suggest placing the catch line after the then lines, but this should have no observable effect on the code's behavior.
Looking at this line of code:
getNodeSubtypesMetadata().catch(nodeSubtypesMetadataErrorCb)
There are two promises. The first is the promise returned by getNodeSubtypesMetadata(), which is rejected. The second promise is returned by catch(nodeSubtypesMetadataErrorCb), which is fulfilled. So, when you assign the result of the above expression to a variable, you are getting the second, fulfilled, promise. Since you are interested in acting on the first promise, you need to change your code to this:
var subtypesMetadataResolved = getNodeSubtypesMetadata();
subtypesMetadataResolved.catch(nodeSubtypesMetadataErrorCb);
subtypesMetadataResolved.then(function(){
debugger
});
subtypesMetadataResolved.then(function () {
debugger
});
Edit: As an alternative, to have two functions that both act on the same fulfilled promise, you can just wrap those with a single function:
function nodeSubtypesMetadataFulfilledCb(metadata) {
createNodes(metadata);
prepareDataForWidgets(metadata);
}
subtypesMetadataResolved.then(
nodeSubtypesMetadataFulfilledCb,
nodeSubtypesMetadataErrorCb);
I have a chain of promises that looks like this:
Transaction.findPublic({}).then(function(transactions) {
combined = combined.concat(transactions);
return JoinEvent.find().exec();
}, function(err) {
return res.status(503).send(err);
}).then(function(joins) {
combined = combined.concat(joins);
return NewCategoryEvent.find().exec();
}, function(err) {
return res.status(503).send(err);
});
It's really unclear if this res.send() will actually exit my promise chain. It might just get sent into the next .then(), which will definitely not work.
I'm trying to test this with the jasmine test framework, but I am using my own mocked res object. The mock obviously does not have logic to exit the function, but I'm wondering if the real express res object does.
Will return res.send() exit this promise chain? If not, can I instead break out by throwing an error?
Transaction.findPublic({}).then(function(transactions) {
combined = combined.concat(transactions);
return JoinEvent.find().exec();
}).then(function(joins) {
combined = combined.concat(joins);
return NewCategoryEvent.find().exec();
}).catch(function(err) {
res.status(503).send(err);
});
Explanation: When you chain promises and a reject(error) occurs, it'll skip all following fullfill(success) callbacks till a rejection(error) callback is found, it calls the first found rejection callback( then continues on to the next promise), if none is found, it throws an error.
also catch(func) is equivalent to then(undefined, func) but more readable.
for more info : promise error handling.
In angular code, I have a chained promise like this:
// a function in LoaderService module.
var ensureTypesLoaded= function(){
return loadContainerTypes($scope).then(loadSampleTypes($scope)).then(loadProjectList($scope)).then(loadSubjectTypes($scope));
}
Each of these functions return a promise, that loads things from a resource and additionally modifies $scope on error and success, e.g.:
var loadProjectList = function ($scope) {
// getAll calls inside a resource method and returns promise.
return ProjectService.getAll().then(
function (items) {
// succesfull load
console.log("Promise 1 resolved");
$scope.projectList = items;
}, function () {
// Error happened
CommonService.setStatus($scope, 'Error!');
});
};
I intent to use in in a code in a controller initialziation as such:
// Part of page's controller initialization code
LoaderService.ensureTypesLoaded($scope).then(function () {
// do something to scope when successes
console.log("I will do something here after all promises resolve");
}, function () {
// do something when error
});
However, this does not work as I'd like to. Ideally message "I will do something here after all promises resolve" must appear after all promises resolve. Instead, I can see that it appears earlier than messages from resolved promises within functions that are listed ensureTypesLoaded.
I would like to create a function ensureTypesLoaded such, that:
it returns a promise that is resolved when all chained loads are resolved;
if any of "internal" promises fail, the function should not proceed to next call, but return rejected promise.
obviously, if I call ensureTypesLoaded().then(...), the things in then() must be called after everything inside ensureTypesLoaded is resolved.
Please help me with correct building of chained promises.
I thinks that problem is in your loadProjectList function. Because .then() should receive function which is called at resultion time. Usually is function returning chain promise.
But in your case you call all load immediately in parallel. It's little complicated with $scope passing. But I think your code should appear like this
//this fn is called immediatly on chain creation
var loadProjectList = function ($scope) {
//this fn is called when previous promise resolves
return function(data) {
//don't add error handling here
ProjectService.getAll().then(...)
}
}
This cause serial loading as you probably want. (just notice: for parallel excution in correct way $q.all is used)
Finally you should have error handler only in ensureTypesLoaded, not in each promise.
I have read up on Kris Kowal's Q and angularjs $q variable for a few hours now. But for the life of me I can't figure out how this works.
At the moment I have this code in my service:
resetpassword: function (email, oldPassword, newPassword) {
var deferred = $q.defer(); //Why am I doing this?
var promise = auth.$changePassword(email, oldPassword, newPassword); //$changepassword in angularfire returns a promise
console.log(deferred); //Object with resolve, reject, notify and promise attrs
var rValue = promise.then(function(){
//successcallback
return 1; //if changepassword succeeds it goes here as expected
}, function(error){
//errorcallback
return error.code; //if changepassword fails (wrong oldpass for example) it goes here, also works
}
);
deferred.resolve(promise); //Should I do this? I thought my promise was resolved in the callbacks?
console.log(rValue); //This outputs another promise? Why not 1 or error.code, how the am I ever going to get these values??
return rValue; //I want 1 or error.code to go back to my controller
},
var deferred = $q.defer(); //Why am I doing this?
It's the deferred anti-pattern that you are doing because you don't understand promises. There is literally never a good reason to use $q.defer() in application logic.
You need to return a promise from your function:
resetpassword: function(email, oldPassword, newPassword) {
return auth.$changePassword(email, oldPassword, newPassword)
.then(function() { return 1; })
.catch(function(e) { return e.code });
}
Usage is:
resetpassword(...)
.then(function(num) {
if (num === 1) {
}
else {
}
});
It helps to think how the function would be written in synchronous code:
resetpassword: function(email, oldPassword, newPassword) {
try {
auth.$changePassword(email, oldPassword, newPassword)
return 1;
}
catch (e) {
return e.code;
}
}
A promise is a function that does not execute immediately. Instead, you are 'promising' to respond to the status of a deferred object whenever it has completed it's processing. Essentially, you are creating an execution chain that simulates multiple threads. As soon as a deferred object is complete, it returns a status code, the calling function is able to respond to the status code, and return it's own status code if necessary, etc.
var deferred = $q.defer();
Here you are creating a deferred object, to hold the callbacks which will be executed later.
var promise = auth.$changePassword(email, oldPassword, newPassword);
This is the actual function, but you are not calling it here. You are making a variable to reference it, so that you can monitor it.
var rValue = promise.then(function(){
The .then() is the function that will be executed if the promise is successful. Here you are inlining a function that will execute upon the success of the promise's work.
}, function(error){
And then chaining the error function, which is to be executed if the promise fails. You are not actually doing anything here other than returning the error code, but you could do something to respond to the failure yourself.
deferred.resolve(promise);
This is the point where you are actually telling your deferred object to execute the code, wait for a result, then execute either the success or the failure code as appropriate. The promise is added to the execution chain, but your code is now free to continue without waiting and hanging up. As soon as the promise is finished and a success or failure is processed, one of your callback functions will process.
console.log(rValue);
Your function is not returning the actual values here because you don't want your function to stop and wait for a response before continuing, since this would block the program until the function completes.
return rValue;
You are returning your promise to the caller. Your function is a promise which is calling a promise which might be calling another promise, etc. if your function was not a promise, then even though the code you are calling is not blocking, your code would become a block as you wait for the execution to complete. By chaining promises together, you are able to define how you should respond without responding at exactly that moment. You are not returning the result directly, but instead 'promising' to return a success or fail, in this case passing through the success or fail from the function you are calling.
You use deferred promises when you are going to perform a task that will take an unknown amount of time, or you don't care when it gets done - an Asynchronous task. All you care about is getting a result back whenever the code you called tells you that the task is completed.
What you should be doing in your function is returning a promise on the deferred that you are creating. Your code that calls resetpassword should then wait for this promise to be resolved.
function (email, oldPassword, newPassword) {
var deferred = $q.defer(); //You are doing this to tell your calling code when the $changepassword is done. You could also return the promise directly from angularfire if you like but of course the return values would be different.
var promise = auth.$changePassword(email, oldPassword, newPassword); //$changepassword in angularfire returns a promise
console.log(deferred); //Object with resolve, reject, notify and promise attrs
promise.then(function(){
//successcallback
deferred.resolve(1); //if changepassword succeeds it goes here as expected
}, function(error){
//errorcallback
deferred.reject(0); //if changepassword fails (wrong oldpass for example) it goes here, also works
}
);
// Quite right, you should not do this.
console.log(rValue); //This outputs another promise? Why not 1 or error.code, how the am I ever going to get these values??
return deferred.promise; // Return the promise to your controller
}
What you have done is mixed promises. As you put in the code comments,
$changepassword in angularfire returns a promise
Basically, var deferred = $q.defer() creates a new promise using deferred.promise, but you are already creating a promise with $changePassword. If I understand what you are trying to do correctly, you would simply need to do this.
resetpassword: function (email, oldPassword, newPassword) {
auth.$changePassword(email, oldPassword, newPassword).then(function(result) {
console.log(result); // success here
}, function(err){
console.log(err); // error here
});
}
EDIT:
Upon Robins comment, if you want to deal with the result in a controller, rather than call the then() function inside the service, return the promise itself to the controller. Here is an example:
Service function:
resetpassword: function (email, oldPassword, newPassword) {
// return the promise to the caller
return auth.$changePassword(email, oldPassword, newPassword);
}
Controller function:
.controller('MyCtrl', ['$scope', 'myService', function($scope, myService){
// get the promise from the service
myService.resetPassword($scope.email, $scope.oldPassword, $scope.newPassword).then(function(result){
// use 1 here
}, function(err){
// use err here
});
}]);