7

Coming from Angular and Ember, where making Ajax requests resulted in a Promise, I find it a little disappointing and frustrating that SFDC wants me to get back into callback hell by setting callbacks on my actions, rather than writing:

var promise = $A.enqueueAction(action);
promise.then(function(result){
   // Do something
});

Is there a good way to make the above (or something like it) work without needing to action.setCallback(this, convertResponseToPromise) every time I use an action, or put the action API into a wrapper which would then become a dependency whenever I wanted to use it?

1

3 Answers 3

2

Yes, in my helper JS file I have utility function to provide me Actions as promises for this very reason.

Snippet below and full code at https://gist.github.com/DouglasCAyers/13db5714c3cae0cbcacd252fba8c127a#file-controller-js-L2

callAction : function( component, actionName, params ) {

    var helper = this;

    var p = new Promise( function( resolve, reject ) {

        helper.showSpinner( component );

        var action = component.get( actionName );

        if ( params ) {
            action.setParams( params );
        }

        action.setCallback( helper, function( response ) {

            helper.hideSpinner( component );

            if ( component.isValid() && response.getState() === 'SUCCESS' ) {

                resolve( response.getReturnValue() );

            } else {

                console.error( 'Error calling action "' + actionName + '" with state: ' + response.getState() );

                reject( response.getError(), response.getState() );

            }
        });

        $A.enqueueAction( action );

    });

    return p;
},

Then in my controller JS or other helper JS functions I can call and chain actions together like this:

someMethod : function( component, event, helper ) {

    var helper = this;

    helper.callAction( component, 'c.someApexMethod', {

        'someParam' : component.get( 'v.someAttribute' )

    }).then( $A.getCallback( function( data ) {

        component.set( 'v.anotherAttribute', data );

    })).catch( $A.getCallback( function( errors, state ) {

        helper.logActionErrors( component, errors );

    }));

}

You may also be interested in Bob Buzzards blog post about same topic: http://bobbuzzard.blogspot.com/2016/12/javascript-promises-in-lightning_30.html

1
  • @Keith C also suggested Bob's blog, which (as you know) offered a similar method. I like the approach, though I don't like that I'd need to either directly include it or require it in every component when I want to use it... Apr 9, 2017 at 21:39
2

Promises are great and I've been promoting them internally at Salesforce for years. But they do not work well when you're building a reactive system and the metadata/data is not immutable. That is the case with Salesforce: effectively all metadata and data is mutable.

Actions, including storable actions (https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_storable_actions.htm), were designed around this principle. The function callback pattern better matches that of reactive systems where the callback may be invoked repeatedly. Contrast this with promises which may only resolve/reject once.

Again promises work wonderfully in some scenarios. I love the ergonomics and functionality. They work well for data mutation in the Salesforce domain where an operation is performed once. But they are not a good fit for reading data in a system based on mutable data.

1
  • can you elaborate -- in a non-abstract way -- on why Bob Buzzard's solution is a bad idea? Or if it isn't a bad idea, why it shouldn't be a native part of the framework? Apr 9, 2017 at 21:42
1

If you still prefer Promise API for your actions, you could try Lax service component - https://github.com/ruslan-kurchenko/sfdc-lax

It provides easy to use API to call server-side actions with Promises, you can call one action, chained actions or even list of actions at ones.

Below is a simple example of chained calls:

component.lax.enqueue('c.getParentValue')
  .then(parentValue => {
      component.set('v.parentValue', parentValue);
      return component.lax.enqueue('c.getDependentValue', { parentValue: parentValue });
  })
  .then(dependentValue => {
      component.set('v.dependentValue', dependentValue);
  });

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .