Chaining promises with AngularJs and JQuery
Promises allow for consistent handling of the results of a task (Usually asynchronous). A promise is returned from a function call with a deferred result.
The promise result is either success (promise.resolve) or failure (promise.reject).
The basic usage pattern looks like this:
1 2 |
doStuff() // returns a promise .then(function(data) { /* handle success */ }, function(data) { /* handle rejection */ }); |
If you must have more than one step then promises can be chained. Each step in the chain should return another promise. If any of the steps get rejected then the next steps are skipped and the next rejection handler is called.
1 2 3 4 |
doStuff1() // should return a promise .then(function (data) { return doStuff2(); /* should return a promise */ }) .then(function (data) { return doStuff3(); /* should return a promise */ }) .then(function (data) { /* all were resolved */ }, function (data) { /* handle rejection from any of the above*/ }); |
AngularJs
Use 'catch' to make it more obvious when the failure handling is.
'finally' indicates a step which always runs.
1 2 3 4 5 6 |
doStuff1() // must return a promise .then(function (data) { return doStuff2(); }) .then(function (data) { return doStuff3(); }) .then(function (data) { /* all steps were resolved */ }); .catch(function () { /* at least one step was rejected */ }) .finally(function () { /* I always run */ }) |
JQuery
Use 'done' to execute code when no rejections occur.
Use 'fail' to make it obvious when the failure handling is.
'always' indicates a step which always runs.
1 2 3 4 5 6 |
doStuff1() .then(function () { return doStuff2(); }) .then(function () { return doStuff3(); }) .done(function () { alert('all worked'); }) .fail(function () { alert('at least one failed'); }) .always(function () { alert('I always run'); }); |
Avoiding easy mistakes:
1. Forgetting to include the return keyword.
1 2 3 4 |
doStuff1() // must return a promise .then(function (data) { doStuff2(); /* must return a promise */ }) .then(function (data) { doStuff3(); /* must return a promise */ }) .then(function (data) { /* all were resolved */ }, function (data) { /* handle rejection from any of the above*/ }); |
In this case when doStuff1() is resolved doStuff2() will be called.
Because it does not return a promise then doStuff3() will be called irrespective to the result of doStuff2()
unless it throws an exception.
i.e. A step must return a promise or throw an exception otherwise the next 'resolved' step will be called.
2. In AngularJs handling failure on every chained step rather than once at the end.
1 2 3 4 |
doStuff1() // must return a promise .then(function (data) { return doStuff2(); }, function(data) { alert('step 1 was rejected'); }) .then(function (data) { return doStuff3(); }, function (data) { alert('step 2 was rejected'); }) .then(function (data) { /* handle resolved from dostuff3 */ }, function (data) { alert('step 3 was rejected'); }); |
If a step should only execute if the previous step was resolved then rejection should be handled after the last step.
The problem with handling it in each 'then' is that if the rejection function does not either return a promise which is rejected or throw an exception then the next chained resolved step will be executed.
e.g. if doStuff1() is rejected then 'alert('step 1 was rejected');' will be executed. Because this does not return a promise the next 'then' statement resolved step will run and 'doStuff3()' will be called.
Note: this is different to the way JQuery works. It will pass the rejected promise to the next '.then' causing each rejection handler to be called.
JQuery Chained Promise Example: http://jsfiddle.net/2DQwx/