Simple example on how to use Promises to call @AuraEnabled Apex methods?

In the Lightning Component Developer Guide you can find that Promises can be used in Lightning Components and what special things we need to care for to make them work. But there is no fully working example on how to implement and use them.

Let’s say we have 3 @AuraEnabled Apex methods we want to call asynchronously after each other using the output of the first as input parameter of the second and so on.

public class elfApexCascade {
    @AuraEnabled public static Object myServerSideAction_1(String input) {
        return input+'.Apex1';
    }
    @AuraEnabled public static Object myServerSideAction_2(String input) {
        return input+'.Apex2';
    }   
    @AuraEnabled public static Object myServerSideAction_3(String input) {
        return input+'.Apex3';
    }   
}

The logic is very simple: it concatenates only the strings and calling the first method with an initial "START" we would expect something like this as result of the last Apex call: "START.Apex1.Apex2.Apex3"

The component itself has only one button to start the cascaded execution.

<aura:component controller="elfApexCascade" implements="flexipage:availableForAllPageTypes,force:hasRecordId" access="global" >
    <button onclick="{!c.onclick}">Apex</button>
</aura:component>

Now invoking that Apex in good old xmas-tree-style looks like this in the controller:

onclick : function(cmp, evt, hlp) {
    var call1 = cmp.get("c.myServerSideAction_1");
    call1.setParams( { input : 'START' } );
    call1.setCallback(null,function(res) {
        if(res.getState()=='SUCCESS') {
            console.log('Call 1 : ', res.getReturnValue() ); 
            var call2 = cmp.get("c.myServerSideAction_2")
            call2.setParams( { input : res.getReturnValue() } );
            call2.setCallback(null,function(res) {
                if(res.getState()=='SUCCESS') {
                    console.log('Call 2 : ', res.getReturnValue() ); 
                    var call3 = cmp.get("c.myServerSideAction_3")
                    call3.setParams( { input : res.getReturnValue() } );
                    call3.setCallback(null,function(res) {
                        if(res.getState()=='SUCCESS') {
                            console.log('Call 3 : ', res.getReturnValue() ); 

                            // ... optionally more nestings here

                        }
                    });
                    $A.enqueueAction(call3);
                }
            });
            $A.enqueueAction(call2);
        }
    });
    $A.enqueueAction(call1);        
},

If you press the button it executes in the console like this:

Call 1 :  START.Apex1
Call 2 :  START.Apex1.Apex2
Call 3 :  START.Apex1.Apex2.Apex3

Can we use Promises for that scenario in order to improve readability and maintainability of that code?

Answer

Using Promises, at least we can make it look like that in the controller:

onclick : function(cmp, evt, hlp) {
    hlp.apex(cmp,'myServerSideAction_1',{ input : 'START-PROMISE' })
        .then(function(result){
            console.log('Call 1 : ' , result );
            return hlp.apex(cmp,'myServerSideAction_2',{ input : result });
        })
        .then(function(result){
            console.log('Call 2 : ' , result );
            return hlp.apex(cmp,'myServerSideAction_3',{ input : result });
        })
        .then(function(result){
            console.log('Call 3 : ', result );
        })
        // optionally more chainings here
    ;
},

In order to make it work we need to wrap the apex invocation in a promise. We can do it easily in the helper, however this pattern is re-usable over many components and we might want to move it out into a separate JS Static Resource later. In the helper it would be:

apex : function( cmp, apexAction, params ) {
    var p = new Promise( $A.getCallback( function( resolve , reject ) { 
        var action                          = cmp.get("c."+apexAction+"");
        action.setParams( params );
        action.setCallback( this , function(callbackResult) {
            if(callbackResult.getState()=='SUCCESS') {
                resolve( callbackResult.getReturnValue() );
            }
            if(callbackResult.getState()=='ERROR') {
                console.log('ERROR', callbackResult.getError() ); 
                reject( callbackResult.getError() );
            }
        });
        $A.enqueueAction( action );
    }));            
    return p;
},

I think the main benefit is chaining promises without that ugly nesting in the vanilla-style. At the same time, we can get rid of that nasty “c.” thing in front of the method name and do us a favor by letting us set the parameters without the verbosity of action.setParams({}). All of this combined is shrinking the code of the business-logic a lot. Also nice is a new way of error-handling.

$A.getCallback()

It is very important to point out the use of $A.getCallback() as stated in the documentation. If you omit it (like I mistakenly did the first time!), you might not even notice it immediately. But you will run into very hard to debug issues later on. Without $A.getCallback() it even worked most of the time, but it felt like having performance issues and the chained invocations came back seriously delayed or only after clicking around on the UI. It led me on a wrong path while asking this question: Lightning Components: why are subsequent invocations of $A.enqueueAction() so super SLOW?

But it had nothing to do with server-side performance or penalties for subsequent calls. It was simply the missing of $A.getCallback()

Attribution
Source : Link , Question Author : Uwe Heim , Answer Author : Uwe Heim

Leave a Comment