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?

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

  • @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... – Brian Kessler Apr 9 '17 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.

  • 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? – Brian Kessler Apr 9 '17 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);
  });

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.