I ran into a small dilemma. In this angularJs app (1.3.7v) I fetch requirements as well as user belongings from the server and perform some front-end validation. Which looks something like this:
validationFunction = function(userId){
return new Promise(function(resolve, reject){
Promise.all([
requirementService.requirementApi.requirements.getAll().$promise,
userBelongingsService.userBelongingsApi.getAll(userId).$promise
]).then(function(requirements,belongings){
console.log(requirements, belongings);
resolve(isValid(requirements, belongings));
}).catch(function (err) {
reject(err);
});
});
}
For reference I also tried
validationFunction = function(userId){
return new Promise(function(resolve, reject){
requirementService.requirementApi.requirements.getAll().$promise.then(function(requirements) {
return [requirements, userBelongingsService.userBelongingsApi.getAll(userId)];
}).then(function(requirements,belongings){
console.log(requirements, belongings);
resolve(isValid(requirements, belongings));
}).catch(function (err) {
reject(err);
});
});
}
The promises are async and so they only get resolved later. But if used with .then() it should wait until they are resolved (rejected or resolved). Although in both methods the promises are resolve(at some time) at the moment when they are passed to the function they are not. And so the function fails saying that they are undefined. So what is the other way or am I missing something? :)
your code can be simplified to
validationFunction = function(userId){
return Promise.all([
requirementService.requirementApi.requirements.getAll().$promise,
userBelongingsService.userBelongingsApi.getAll(userId).$promise
]).then(function([requirements,belongings]){
console.log(requirements, belongings);
return isValid(requirements, belongings);
});
};
However, that contains some es2015+ specifics
so, in less modern JS
validationFunction = function(userId){
return Promise.all([
requirementService.requirementApi.requirements.getAll().$promise,
userBelongingsService.userBelongingsApi.getAll(userId).$promise
]).then(function(results){
console.log(results[0], results[1]);
return isValid(results[0], results[1]);
});
};
edit: sorry, I missed a ) in the above code originally
To expand on the answer a little (got distracted with the real world) - Firstly, as #Bergi pointed out, Avoid the Promise constructor antipattern! - as you are dealing with functions that return a promise (Promise.all, ....getAll().$promise, ....getAll(userId).$promise) there's no need to wrap all that in a new Promise
Secondly, the onfulfilled callback of .then is called with a single argument, the resolved value. In the case of Promise.all, this is an array of resovled values, in the same order as they appear in the Promise.all argument. That's why the ES2015+ version can use the ([a, b]) form for the function argument (sorry, I can't recall or find the "name" for this form). It's important to note that if either one of those Promises in the Promise.all array are rejected, Promise.all rejects with the rejected value of the (first) rejected promise (that's a lot of rejection in that sentence!)
You can also remove the .catch portion of your code, as the error handling can (and should) be done by the calling code to your function
validationFunction(someUserId)
.then(function(result) {
// this will be the value of isValid(requirements, belongings)
})
.catch(function(err) {
// do some error handling here
});
and finally, in it's most ES2015+ form (that I can think of)
validationFunction = userId => Promise.all([
requirementService.requirementApi.requirements.getAll().$promise,
userBelongingsService.userBelongingsApi.getAll(userId).$promise
]).then(([requirements, belongings]) => isValid(requirements, belongings));
Related
My Promise issue
I am new to Promises and I've been reading the Q Documentation, where it says:
When you get to the end of a chain of promises, you should either return the last promise or end the chain.
I have defined a Promise in my code the Q.Promise way, with the following console.logs to log out an execution trace:
function foo(){
return Q.Promise(function(resolve, reject) {
doSomething()
.then(function() {
console.log('1');
return doSomething1();
})
.then(function() {
console.log('2');
return doSomething2();
})
.then(function() {
console.log('3');
return doSomething3();
})
.catch(function(err) {
console.log('catch!!');
reject(err);
})
.done(function() {
console.log('done!!');
resolve();
});
});
}
In case every doSomethingN() executes correctly, everything works as intended and I get the expected trace:
1
2
3
done!!
But in case any of the doSomethingN() fails:
foo() works correctly, because the error function callback is the one that runs whenever a reject(err) occurs:
foo().then(function() { /* */ }, function(err) { /* this runs! */ });
And I get the following trace (ie. when doSomething1() fails):
1
catch!!
done!!
My question
What I thought at first was the following:
Okay, let's handle the chaining success and failure in both: .done() and .catch() methods. If everything goes well .done()'s callback will be executed and the promise will be resolved. In case there's an error at any point, .catch()'s callback will be executed and the promise will be rejected - and because of that, done() won't be executed.
I think I am missing something about how the .done() works... because by having a look at my logging trace, I realized that .done() seems to be executing always - whether there is an error and .catch() is executed or not - and that is what I wasn't expecting.
So, after that, I removed .done()'s callback and now foo():
works if there's an error during the chain execution
does not work if everything works correctly
What should I reconsider and how could/should I make it work?
catch(cb) is just an alias for then(null, cb), and you've actually fixed an error in catch, so flow naturally turned to success result in done.
If you want to just decorate the error in catch, you should rethrow the error afterwards, e.g. proper passthru may look as:
catch(function (err) {
console.log(err);
throw err;
});
Still your example doesn't make much sense. You should never use done, when you return a promise. If you want to resolve initialized promise with internally created chain of promises, you should just resolve it as:
resolve(doSomething()
.then(function() {
console.log('1');
return doSomething1();
})
....
.then(function() {
console.log('N');
return doSomethingN();
}));
There's no need for internal error handling, leave that to consumer of promise which you return.
And other point. If when creating new promise you know it will be resolved with other one, then there's no logical reason to create such promise, just reuse one you planned to resolve with. Such error was also coined as deferred anti-pattern
You should consider doing this:
function foo() {
// Calling .then() on a promise still return a promise.
// You don't need Q.Promise here
return doSomething()
.then(function(doSomethingResult) {
console.log('1');
return doSomething1();
})
.then(function(doSomething1Result) {
console.log('2');
return doSomething2();
})
.then(function(doSomething2Result) {
console.log('3');
return doSomething3();
});
}
foo()
.then(function(fooResult) {
console.log(fooResult); // fooResult should be what is returned by doSomething3()
})
.catch(function(err) {
console.error(err); // Can be thrown by any
})
.done(function() {
console.log('I am always executed! error or success');
});
If you want to return a promise, in most cases it does not make much sense to use catch (unless you want to recover potential errors). It never make sense to use done in a method returning a promise. You would rather use these methods at the very end of the chain.
Notice that doSomethingX() can return either a value, or a promise, it will work the same.
You can make it work by resolving promise in your last then callback.
function foo(){
return doSomething()
.then(function() {
console.log('1');
return doSomething1();
})
.then(function() {
console.log('2');
return doSomething2();
})
.then(function() {
console.log('3');
return doSomething3();
})
}
Consider using bluebird for promises. It has many useful features as compared to any other promise library. You may find it difficult to begin it, but once you get hold of it you're going to love it.
I'm using native promises (mostly) and attempting to recover from an error and continue executing the promise chain.
Effectively, I'm doing this:
REST query to see if ID exists. Note that this returns a jquery deferred.
.then (success means ID exists, so fail and stop)
(fail means ID does not exist, so continue creating ID)
.then (create the ID record and send to the server)
I return a Promise.resolve() from my rejected function, which should cause the success part of the next .then to execute. It does not. I've tried this on Chrome and Safari.
Note that the first promise is actually a query deferred, but according to this page (http://api.jquery.com/deferred.then/), deferred.then() returns a promise object. So adding an extra .then should covert to native promises.
To make it clearer - here's the pseudocode:
promise = $.ajax(url);
promise = promise.then(); // convert to promise
promise.then(function() { cleanup(); return Promise.reject(); },
function(err) { return Promise.resolve(); });
.then(function() { createIdentityDetails(); });
.then(function() { sendIdentityDetails(); });
Note that I want to FAIL when the ajax returns success, and I want to
continue processing when the ajax call fails.
What happens is that the FAIL functions for all subsequent .then portions execute. That is, my return Promise.resolve() doesn't work - which is (I think) in violation of the spec.
I'd appreciate any feedback on how I can deal with and recover from errors in long promise chains.
Many thanks for any advice you can provide.
p.s. creating and collecting the full identity information is quite time consuming, so I don't want to do it if the ID exists. Hence I want to check first and fail quickly.
p.p.s I really like the way that promises have unwound these deeply nested async callback chains.
Assuming createIdentityDetails() and sendIdentityDetails() to be promise-returning asynchronous functions ...
If what we see in the question is the entirety of the promise chain, then handling the error condition is simple. It's not necessary to convert success to failure or failure to success, or from one type of promise to another.
$.ajax(url).then(function() {
cleanup();
}, function(err) {
createIdentityDetails()
.then(sendIdentityDetails);
});
This will work regardless of the type of promise returned by createIdentityDetails() jQuery or non-jQuery.
If, however, there's more to it, eg a caller function needs to be informed of the outcome, then you need to do more, and it depends on how you want the possible outcomes to be reported.
Report 'ID already exists' as failure and 'new ID created' as success
This is what the question suggests
function foo() {
return $.ajax(url).then(function() {
cleanup();
return $.Deferred().reject('failure: ID already exists');
}, function(err) {
return createIdentityDetails()
.then(sendIdentityDetails)
.then(function() {
return $.when('success: new ID created');
});
});
}
Report both types of outcome as success
This seems more sensible as the handled error will be reported as success. Only unpredicted, unhandled errors will be reported as such.
function foo() {
return $.ajax(url).then(function() {
cleanup();
return 'success: ID already exists';
}, function(err) {
return createIdentityDetails()
.then(sendIdentityDetails)
.then(function() {
return $.when('success: new ID created');
});
});
}
Whichever reporting strategy is adopted, it matters very much what type of promise createIdentityDetails() returns. As the first promise in the chain it determines the behaviour of both its chained .thens.
if createIdentityDetails() returns a native ES6 promise, then no worries, most flavours of promise, even jQuery, will be assimilated.
if createIdentityDetails() returns a jQuery promise, then only jQuery promises will be assimilated. Therefore sendIdentityDetails() must also return a jQuery promise (or an ES6 promise which must be recast into jQuery with $.Deferred(...)), as must the final success converter (as coded above).
You can see the effects of mixing jQuery and ES6 promises in these two ways here. The first alert is generated by the second block of code, and is not what is expected. The second alert is generated by the first block and correctly gives the result 98 + 1 + 1 = 100.
promise = promise.then(); // convert to promise
Huh? A promise returned by $.ajax is already a promise.
promise.then(function() { cleanup(); return Promise.reject(); },
function(err) { return Promise.resolve(); });
The problem with this is that jQuery is not Promises/A+ compatible, and fails to adopt promises/thenable from other implementations than its own. You would have to use $.Deferred here to make this work, like
promise.then(function() { cleanup(); return $.Deferred().reject(); },
function() { return $.when(); }); // or return $.Deferred().resolve();
That is, my return Promise.resolve() doesn't work - which is (I think) in violation of the spec.
Indeed it is. However, jQuery is known for this, and they won't fix it until v3.0.
To get the native Promise library you want to use working, you will need to avoid jQuery's then. This can easily be done:
var $promise = $.ajax(url);
var promise = Promise.resolve($promise); // convert to proper promise
promise.then(function() {
cleanup();
throw undefined;
}, function(err) {
return undefined;
})
.then(createIdentityDetails)
.then(sendIdentityDetails);
It seems that JQuery promises do not permit you to change a failure to a success. If, however, you use native promises, you can.
For example:
Promise.resolve()
.then(function() {console.log("First success"); return Promise.reject(); },
function() { console.log("First fail"); return Promise.resolve(); })
.then(function() {console.log("Second success"); return Promise.reject(); },
function() { console.log("Second fail"); return Promise.resolve(); })
.then(function() {console.log("Third success"); return Promise.reject(); },
function() { console.log("Third fail"); return Promise.resolve(); })
Here I return a reject from the first success handler. In the second failure handler I return a resolve. This all works as expected. The output is (Chrome):
First success
Second fail
Third success
It turns out the proper way to deal with jQuery deferreds and promises is to cast them:
var jsPromise = Promise.resolve($.ajax('/whatever.json'));
(from http://www.html5rocks.com/en/tutorials/es6/promises/).
This works nicely, so if you change the initial line above to:
Promise.resolve($.ajax("this will fail"))
...
you correctly get:
First fail
Second success
Third fail
Bottom line... cast deferred to promise asap, then everything seems to work right.
Hopefully this will clear things up a bit, you had a couple of stray ; and you're doing things you don't really need to do in the then functions
firstly, I'm sure you DO NOT want the
promise = promise.then();
line, the code would look like this
promise = $.ajax(url);
promise.then(function() {
cleanup();
throw 'success is an error'; // this is equivalent to return Promise.reject('success is an error');
}, function(err) {
return 'failure is good'; // returning here means you've nullified the rejection
}) // remove the ; you had on this line
.then(function() { createIdentityDetails(); }) // remove the ; on this line
.then(function() { sendIdentityDetails(); }) // remove the ; on this line
.catch(function(err) { }); // you want to catch the error thrown by success
I want to fulfill a promise with some other promise. The point is that I really want to get access to the (still pending) second promise as soon as the first promise is fulfilled. Unfortunately, I only seem to be able to get the second promise's resolution value once both both promises are fulfilled.
Here's the use case that I have in mind:
var picker = pickFile();
picker.then( // Wait for the user to pick a file.
function(downloadProgress) {
// The user picked a file. The file may not be available just yet (e.g.,
// if it has to be downloaded over the network) but we can already ask
// the user some more questions while the file is being obtained in the
// background.
...do some more user interaction...
return downloadProgress;
}
).then( // Wait for the download (if any) to complete.
function(file) {
// Do something with the file.
}
)
The function pickFile displays a file picker where the user may pick a file either from their own hard drive or from a URL. It returns a promise picker that is fulfilled as soon as the user has picked a file. At this point, we may still have to download the selected file over the network. Therefore, I cannot fulfill picker with the selected file as resolution value. Instead, picker should be fulfilled with another promise, downloadProgress, which in turn will eventually be fulfilled with the selected file.
For completenes, here's a mock implementation of the pickFile function:
function pickFile() {
...display the file picker...
var resolveP1 = null;
var p1 = new Promise(
function(resolve, reject) {
resolveP1 = resolve;
}
);
// Mock code to pretend the user picked a file
window.setTimeout(function() {
var p2 = Promise.resolve('thefile');
resolveP1(p2); // <--- PROBLEM: I actually want to *fulfill* p1 with p2
}, 3000);
return p1;
}
The problem in the marked line is that I would like to fulfill the promise p1 with the new promise p2, but I only know how to resolve it. The difference between fulfilling and resolving is that resolving first checks if the supplied value p2 is again a promise. If it is, then fulfillment of p1 will be deferred until p2 is fulfilld, and then p1 will be fulfilled with p2's resolution value instead of p2 itself.
I could work around this issue by building a wrapper around p2, i.e. by replacing the line
resolveP1(p2); // <--- PROBLEM: I actually want to *fulfill* p1 with p2
from the second code example by
resolveP1({promise: p2});
Then, in the first code example, I'd have to replace the line
return downloadProgress;
by
return downloadProgress.promise;
But this seems like a bit of a hack when all I really want to do is just fulfill (instead of resolve) a promise.
I'd appreciate any suggestions.
There doesn't seem to be a solution apart from the workaround I already described in the question. For future reference, if you want to fulfill (rather than resolve) a promise p with a value val, where val is another promise, then just calling the promise resolution function for p with argument val won't work as expected. It would cause p to be "locked in" on the state of val, such that p will be fulfilled with val's resolution value once val is fulfilled (see spec).
Instead, wrap val in another object and resolve p with that object:
var resolveP; // Promise resolution function for p
var p = new Promise(
function(resolve, reject) {
resolveP = resolve;
}
);
function fulfillPwithPromise(val) { // Fulfills p with a promise val
resolveP({promise: val});
}
p.then(function(res) {
// Do something as soon as p is fulfilled...
return res.promise;
}).then(function(res) {
// Do something as soon as the second promise is fulfilled...
});
This solution works if you already know that val is a promise. If you cannot make any assumptions about val's type, then you seem to be out of luck. Either you have to always wrap promise resolution values in another object, or you can try to detect whether val has a field then of type "function" and wrap it conditionally.
That said, in some cases the default behavior of promise resolution may actually have the desired effect. So only use the workaround described above if you are sure that you want to fulfill instead of resolve the first promise with the second one.
Although different people use different terms, in common terminology, "fulfill" means to put a promise in the "success" state (as opposed to "reject")--the state that will trigger then then handlers hanging off it.
In other words, you cannot "fulfill" a promise with a promise. You can fulfill it with a value. (By the way, the term "resolve" is usually meant as either of fulfilling or rejecting.)
What you can do is return a promise from a .then handler and that will have the effect of essentially replacing the original promise with the returned promise.
Here is a simple example of doing that:
asyncTask1 . then(asyncTask2) . then(processData)
where asyncTask1 is a promise, and asyncTask2 is a function which returns a promise. So when asyncTask1 is fulfilled (done successfully), then asyncTask2 runs, and the promise returned by the .then is "taken over" by the promise asyncTask2 returns, so that when it finishes, the data can be processed.
I can do something similar by calling Promise.resolve with a promise as parameter. It's a bit of a misnomer, because I'm not resolving the promise in the technical sense. Instead, the new promise created is "inhabited" by the promise I passed in. It's also useless, because using the result is exactly the same as using the promise I passed in:
Promise.resolve(asyncTask2)
behaves exactly the same as
asyncTask2
(assuming asyncTask2 is already a promise; otherwise Promise.resolve has the effect of creating a promise which is immediately fulfilled with the passed in value.)
Just as you can pass a promise to Promise.resolve, you can pass a promise to the resolve function provided to you as a parameter of the promise constructor callback. If the parameter you pass to resolve is a non-promise, the promise immediately fulfills with that value. However, if the parameter you pass to resolve is another promise, that promise "takes over the body" of the promise you are constructing. To put it another way, the promise you are constructing starts to behave exactly as the the promise passed to resolve.
By "behave exactly" I mean, if the promise you pass in to resolve is already fulfilled, the promise you are constructing is instantly fulfilled with the same value. If the promise you pass in to resolve is already rejected, the promise you are constructing is instantly rejected with the same reason. If the promise you pass in to resolve is not resolved yet, then any then handlers you hang off the promise you are constructing will be invoked if and when the promise you pass to resolve is resolved.
Just as it is confusing that Promise.resolve may result in a promise which is not actually resolved, it is similarly confusing that calling the resolve function handed to you as a parameter to the promise constructor may not actually resolve the promise being constructed if you call it with an unresolved promise. Instead, as I've said a couple of times now, it has the effect of putting the promise being constructed in a state of total congruence with the promise passed to resolve.
Therefore, unless I am missing the point of your question, pickfile could be written as
function pickFile() {
return new Promise(function(resolve, reject) {
...display the file picker...
// Mock code to pretend the user picked a file
window.setTimeout(function() {
resolve('thefile');
});
}
I didn't really understand your question clearly, so this might not be what you want. Please clarify if you care to.
Found a similar solution in the process of moving away from Angular's $q to the native Promise feature. Promise.all could be an option (in cases of independent parallel async tasks) by passing around an appropriate object, or something decorated with the state, passing it off to whatever is ready when appropriate. In the Promise.all sample below note how it recovers in one of the promises--took me awhile to realize how to redirect the result of a chain. The result of the all is just the last promise's return. While this doesn't answer the question's title, using return Promise.reject(<an-object-including-a-promise>) (or resolve) gives a series and/or group of async tasks shared access and control along the way. In the case of picking, downloading then working with a file I'd take out the progress-event handling then do: pickFile.then(download,orFailGracefully) with downloadProgress handled within the download onResolve handler (download-progress doesn't appear to be an async task). Below are related experiments in the console.
var q = {
defer: function _defer(){
var deferred = { };
deferred.promise = new Promise(function(resolve, reject){
deferred.resolve = resolve;
deferred.reject = reject;
});
return deferred;
}
};
var communityThatCares = q.defer();
communityThatCares.promise.then(function(someGood){
console.log('someGood', someGood);
return someGood;
}, function(someBad){
console.warn('someBad', someBad);
return someBad;
});
(new Promise(function(resolve, reject){ communityThatCares.about = 'communityThatCares'; setTimeout(resolve,1000, communityThatCares); }))
.then(
function(e){
console.log(3,e); return e.resolve(e);
}, function(e){
console.warn(3, e); return e.reject(e);
});
var todo = {
find:'swan'
};
var greaterGood = [(
(new Promise(function(res,rej){ res(todo); })).then(function(e){ e.stuff = 'things'; return e; }),
(new Promise(function(res,reject){
reject(todo);
})).then(function(e){ return e; }
,function(e){
console.warn(1,e);
e.recover = 'uh oh';
return Promise.resolve(e);
}).then(function(e){ console.log(2,e); return e; }),
(new Promise(function(res,rej){ res(todo); })).then(function(e){ console.log(1,e); e.schedule = 'today'; return e; },function(e){ console.warn(1,e); return e; }).then(function(e){ console.log(2,e); return e; }))
];
var nkay = Promise.all( greaterGood )
.then(function(todo){
console.log('all',todo[0]); return todo;
}, function(todo){
console.warn('all',todo[0]); return todo;
});
This question already has answers here:
How angular promise .then works
(2 answers)
Closed 7 years ago.
All:
I am pretty new to Promise, just curious how they get resolved, one thing confuse me is:
Some posts show using
var defer = $q.defer();
// some logic block
{
// if success doing something
defer.resolve();
}
return defer.promise;
But if use .then() function, promise is returned from .then(function(){}), I wonder how do I control if this promise resolved or not?
Another confuse is: If I use some chained .then() function, I wonder what is the relationship between them, are they same promise object which is just passed down or each .then will generate a new Promise object and return it?
As specified in this throughout and clear document:
QUESTION 1. I wonder how do I control if this promise resolved or not?
One of the Promise APIs support pecial functions that resolve() or reject() a Promise. So you may use the following functions in your code
var promise = new Promise(function(resolve, reject) {
// do a thing, possibly async, then…
if (/* everything turned out fine */) {
resolve("Stuff worked!");
}
else {
reject(Error("It broke"));
}
});
Rejections happen when a promise is explicitly rejected, but also implicitly
if an error is thrown in the constructor callback.
var jsonPromise = new Promise(function(resolve, reject) {
// JSON.parse throws an error if you feed it some
// invalid JSON, so this implicitly rejects:
resolve(JSON.parse("This ain't JSON"));
});
jsonPromise.then(function(data) {
// This never happens:
console.log("It worked!", data);
}).catch(function(err) {
// Instead, this happens:
console.log("It failed!", err);
});
In other variants the Promise is resolved with the return value that is passed to the next link in the chain.
QUESTION 2.
Promises are in some sense functions that will result in the future with some value. The result value is the return value from promise - so basically promise chaining ( .then(...).then... ) are chain of functions that wait till the previous one will end ( resolve with some value ). Then they are called with an argument which is the return value of the last executed function in the queue ( previous link in the chain ).
.then() returns a new promise object thus allowing chaining. (see remark for documentation link)
REMARK
There is great and small description of all Angular promises in official documentation under the section Promise API and next one - Chaining the promises.
This isn't an attempt to explain promises in their full glory - there are blogs for that. This is to answer your specific questions:
Q1:
But if I use .then() function, promise is returned from .then(function(){}), I wonder how do I control if this promise resolved or not?
The resolve handler function of .then controls how this promise is resolved:
If the handler function returns a non-promise value, then the promise with resolve with that value.
var thenPromise = originalPromise.then(function success() {
return "foo";
});
thenPromise.then(function(data){
console.log(data); // "foo"
});
If the handler function returns another promise, then the .then promise will resolve exactly how the new promise would resolve (or reject)
var thenPromise = originalPromise.then(function() {
return $timeout(function(){ return "foo"; }, 1000);
});
thenPromise.then(function(data){
console.log(data); // (after 1 second) "foo"
});
If the handler function throws an exception or if the the return is an explicitly rejected promise `$q.reject:
var thenPromise = originalPromise.then(function() {
return $q.reject("some error");
});
thenPromise.then(function(data){
console.log(data); // doesn't get here
})
.catch(function(err){
console.log(err); // "some error"
});
Q2:
If I use some chained .then() function, I wonder what is the relationship between them, are they same promise object which is just passed down or each .then will generate a new Promise object and return it?
Each .then generates its own promise.
var timeoutPromise = $timeout(afterTimeout, 1000);
var thenPromise = timeoutPromise.then(doSomething);
var anotherThenPromise = timeoutPromise.then(doSomethingElse);
If timeoutPromise resolves, then both doSomething and doSomethingElse would execute and depending on their outcome thenPromise and anotherThenPromise would have their respective resolutions.
I'm a bit of a novice with promises/Deferreds. Is there a good pattern to handle the case where one might want to short circuit a chain of promises, for both success and error cases? In the error situation, I know you can chain a .then(null, function(error) {}) to the end and catch an error from any of the previous thens, but what if you want to handle an error in a more custom way and terminate? Would you specify a 'type' of error in an earlier error handler and return it via a new promise, to be handled or skipped in the final error handler? And what about a success case, where you want to terminate earlier in the chain (only conditionally firing off any later then's)?
Typically, the promise chain starts with a call to some asynchronous function as such:
var promise = callAsync();
If you are chaining a second async call, you probably do something like this:
var promise = callAsync()
.then(function(){
return callOtherAsync();
})
.then(function(){
return callSuccessAsync();
}, function(){
return callFailAsync();
});
As a result of chaining, promise now contains the final promise which completes when callFinalAsync()'s promise completes. There is no way to short circuit the final promise when using this pattern - you can return a failed promise along the way (for instance, rather than returning the result of callOtherAsync) but that requires the failed promise to progress through the chain (thus causing callFailAsync to be called).
You can always fulfill or reject the promise from within the callbacks as such
var promise = callAsync()
.then(function(){
if(fail){
promise.reject();
//no way to halt progression
}else{
return callOtherAsync();
}
})
.then(function(){
return callSuccessAsync();
}, function(){
return callFailAsync();
});
however, this will not prevent calls to callFailAsync(). Some Promise/A implementations expose a stop method for just this purpose. With stop, you could do this:
var promise = callAsync();
.then(function(){
if(fail){
this.stop();
promise.reject();
}else{
return callOtherAsync();
}
})
.then(function(){
return callSuccessAsync();
}, function(){
return callFailAsync();
});
Which depends on having access to the intermediate promise with this. Some Promise implementations forbid that (forcing this to be window/null/etc), but you can deal with that with a closure.
TL;DR: Promise/A spec doesn't provide a chain short circuit function, but it's not hard to add one.
not sure about jQuery but at least in any Promises/A+ you can just throw:
.then(function() {
if (skip) {
throw new Error("skipping");
}
})
//Chain of thens
.then(...)
.then(...)
.then(...)
.then(...)
.catch(function(){
//skipped here
});
I assume your use case looks like:
promise
.then(a)
.then(b); // We want to have an option to break here
.then(c)
.done(d)
Logical way to handle this is:
promise
.then(a)
.then(function (result) {
if (something) throw new Error("Do not proceed!");
return b(result).then(c).then(d);
}).done();
If you don't like nesting, you may compose b(result).then(c).then(d) as outer function.
I had this exact problem in my application, and achieved short-circuit/cancellation through use of a simple cancellation token object that can be checked for in a Promise's exception/rejection handler callback. Maybe not the most elegant solution, but seems to work well enough without the need for additional libraries or alternate/non-standard Promise implementations
const cancellationToken = {};
somePromiseReturningMethod(...)
.then(doSomething)
.then(doSomethingElse)
.catch(err => {
if (err === cancellationToken)
{
// handle cancellation here and return
}
// handle "regular" errors here (show/log a message, etc)
});
function doSomething(dataFromPromise)
{
// check for whatever condition should result in cancellation/short-circuit
if (...)
{
return Promise.reject(cancellationToken);
}
// carry on as normal...
}