Recently I was working with closure actions and learned something new about promises in the process. So you’re probably familiar with this:

promise . then (() => { console . log ( ' success ' ); }, () => { console . log ( ' error ' ); }). finally (() => { console . log ( ' finally ' ); });

If the promise resolves, “success” and “finally” are logged. If the promise rejects, “error” and “finally” are logged. Now what about this?

promise . catch (() => { console . log ( ' catch ' ); }). then (() => { console . log ( ' success ' ); }, () => { console . log ( ' error ' ); }). finally (() => { console . log ( ' finally ' ); });

If you aren’t familiar with catch() , it is syntactic sugar for then(undefined, onRejection) .

Similarly, if the promise resolves, “success” and “finally” are logged. However, if the promise rejects, “catch”, “success”, and “finally” are logged. This threw me off for a bit. Why is the success handler still getting called when the promise rejects? If you’ve worked with jQuery promises either by themselves or with a library like Backbone, it doesn’t work like this. If a promise rejects, each reject handler in the promise chain gets called. Try it yourself here. This is because jQuery promises are not compliant with the Promise/A+ standard whereas RSVP is, and the standard states:

2.2.7.1 If either onFulfilled or onRejected returns a value x, run the Promise Resolution Procedure [Resolve].

2.2.7.2 If either onFulfilled or onRejected throws an exception e, promise2 must be rejected with e as the reason.

You can read more about the Promises/A+ standard here.

So how do you make subsequent reject handlers get called in the promise chain? Simply throw the error.

promise . catch (( error ) => { console . log ( ' catch ' ); throw error ; }). then (() => { console . log ( ' success ' ); }, () => { console . log ( ' error ' ); }). finally (() => { console . log ( ' finally ' ); });

Now what gets logged is “catch”, “error”, and “finally”. Try it yourself here

So you might be wondering, when would you be in a situation where reject handlers are specified before any success handlers in the promise chain? Recently I found myself in this situation when I was using closure actions.

// app/controllers/new-post.js export default Ember . Controller . extend ({ actions : { createPost ( postData ) { let post = this . store . createRecord ( ' post ' , postData ); return post . save (). catch (( error ) => { this . set ( ' error ' , ' Oops, something went wrong! ' ); throw error ; }); } } });

Here there is a createPost action on the controller for the new-post route. This action creates a post record, saves it, and if there are any errors, render them at the top of the page somewhere. In the template, the action is passed to the post-form component as a closure action.

<!-- app/templates/new-post.hbs --> <h1>Create Post</h1> {{post-form submit=(action 'createPost')}}

In the post-form component template, an action named save is called when the form is submitted.

<!-- app/templates/components/post-form.hbs --> <form onsubmit={{action 'save'}}> ... </form>

The save action on the component then calls the submit closure action.

// app/components/post-form.js export default Ember . Component . extend ({ actions : { save () { let postData = this . get ( ' postData ' ); return this . get ( ' submit ' )( postData ). then (() => { // do stuff like clear out the form }, () => { // do stuff like highlight invalid fields }); } } });

In this example with a closure action, the promise chain executed in the following order, where the reject handler from catch was executed before the success handler.

// executed first let promise = post . save (). catch (( error ) => { this . set ( ' error ' , ' Oops, something went wrong! ' ); throw error ; }); // executed second promise . then (() => { // do stuff like clear out the form }, () => { // do stuff like highlight invalid fields });

Without that throw error in catch , both the catch handler and the success handler would run, which wasn’t expected.

TL;DR If both success and reject handlers are executing unintentionally, you might be forgetting to throw after you catch in the promise chain.

Disclaimer: Any viewpoints and opinions expressed in this article are those of David Tang and do not reflect those of my employer or any of my colleagues.