11

As we know enqueueing multiple actions are all sent to the server at the same time.

What is the suggested approach when need to load multiple data in initialization for lightning component

The issue I am having is that the time each action takes to complete is not the same. Lets say I want to show a spinner until the all actions have completed. How can I tell that all are done.

The setup is basically:

doInit: function (component, event, helper) {

    var action1 = component.get(.....);

    action1.setCallback(this, function (result) {
        ......
    });

    var action2 = component.get(.....);

    action2.setCallback(this, function (result) {
        ......
    });

    var action3 = component.get(.....);

    action3.setCallback(this, function (result) {
        ......
    });


    $A.enqueueAction(action1);
    $A.enqueueAction(action2);
    $A.enqueueAction(action3);

}

Now I want to do this when and only when ALL 3 actions have completed:

$A.util.addClass(component.find("spinner"), "slds-hide");

Assuming the length of time it takes to make the roundtrip for each action can be variable and unpredictable just placing it inside a give callback may remove the spinner while other actions are still processing.

So the question, where to put that line to ensure it is only done once all actions have made the roundtrip?

  • I asked the same question and here is the answer I am using in my org: salesforce.stackexchange.com/a/161972/16304 – javanoob Apr 21 '17 at 18:30
  • 1
    @javanoob dang my search foo failed me. Lots of good answers here. Now promise or counter hmmmmm – Eric Apr 21 '17 at 18:32
9

Eric, I like to use Promises for that. This is my preferred pattern

First Define each promise (I do this in the helper)

myPromise1 : function (component) {
        var action = component.get('c.MyMethod');
        var myParam1 = component.get("v.myParam1");
        var myParam2 = component.get("v.myParam2");

        return new Promise(function (resolve, reject) {
            action.setParams({
                myParam1: myParam1,
                myParam2: myParam2
            });

            action.setCallback(this, function (response) {
               var state = response.getState();

                if (component.isValid() && state === "SUCCESS") {
                    resolve(response.getReturnValue());
                }
                else if (component.isValid() && state === "ERROR") {
                    var errors = response.getError();
                    reject(response.getError()[0]);
                }
            });

            $A.enqueueAction(action);
        });
    },

Then call the promises

Promise.all([this.myPromise1(component), this.myPromise2(component)]).then(function(results) {
            var p1Results = results[0]; //Results from Promise 1
            var p2Results = results[1]; //Results from Promise 2

           //Do your thing
        }).catch(function (err) {
          //Handle errors on any promise here
        });

I love the idea of having just one error handler, and the results come neatly in an array (ordered in the same ordered you called the promises in your first line above)

=== Additional Information ===

If you ever want to have a reusable promise (but don't want to call more than 1 thing with it), you can define it as above and then call it like a function

this.myPromise1(component).then(function(results) {
            var resultsGoHere = results;

            //Do your thing
        }).catch(function (err) {
            //Handle Error
        });

Here are a few resources I loved (the first one was where I learned, the next two where I made sure not to mess up). There is too much info to copy (the relevant parts are above anyway) but hopefully you can get some ideas for more complex patterns (like the Promise.race);

  • Same question as to other poster - Does this approach affect the bundling of the enqueued actions or are they still all sent at the same time. Concerned that by putting the enqueue inside a promise would cause them to be executed independently. – Eric Apr 21 '17 at 18:12
  • 1
    They are all sent at the same time. Defining a promise does not call anything. Once you call it, then it actually executes. – Sebastian Kessel Apr 21 '17 at 18:13
  • 1
    Thanks. I'll give it a shot this weekend and see how it goes. – Eric Apr 21 '17 at 18:14
  • I've had a lot of success using them. I'm going to be adding more resources in this answer so you have more to draw from – Sebastian Kessel Apr 21 '17 at 18:15
  • 1
    All answers are god but accepted this one for the additional detail. Wish I could accept them all but upvited the others. I went with javanoob comment of a counter for now but believe the promise is more correct and will be switching to this method for production quality code and not the demo I am quickly banging out. – Eric Apr 22 '17 at 19:20
6

On using promises, watch out for this gotcha described in Using JavaScript Promises:

Don’t Use Storable Actions in Promises

The framework stores the response for storable actions in client-side cache. This stored response can dramatically improve the performance of your app and allow offline usage for devices that temporarily don’t have a network connection. Storable actions are only suitable for read-only actions.

Storable actions might have their callbacks invoked more than once: first with cached data, then with updated data from the server. This doesn't align well with promises, which are expected to resolve or reject only once.

that is problematic if e.g. you want cached meta data.

Storable actions are described here Storable Actions. I'm using them so I can reference meta data (labels and picklist values that hopefully the platform will provide mechanisms for in the future) in multiple components with the server request only being made once. (Seems somewhat like Angular's $http {cache: true} option.)

  • Hmmm....What defines a "Storable Action"? I am going to google it. I am just setting attribute values (which will be used later) with these actions so would it be ok to use promises here? – Eric Apr 21 '17 at 18:25
  • That's what i've used them for – Sebastian Kessel Apr 21 '17 at 18:28
  • 1
    @Eric I added a link to the storable actions stuff. If you are not using them then not a problem. – Keith C Apr 21 '17 at 18:46
4

You could utilize promises if you want to perform some action when all the actions complete. Here's a barebones example:

testPromises: function(cmp) {
    var p1 = this.serverAction(cmp, 'test');
    var p2 = this.serverAction(cmp, 'test');
    var p3 = this.serverAction(cmp, 'test');

    Promise.all([p1,p2,p3]).then($A.getCallback(function(results){
       $A.util.addClass(component.find("spinner"), "slds-hide");
    }));
},

serverAction : function(component, method, params) {
    var self = this;
    return new Promise(function(resolve, reject) {
        var action = component.get('c.' + method);

        if(params != null)
            action.setParams(params);

        action.setCallback(self, function(response){
            var state = response.getState();

            if(state == 'SUCCESS')
                resolve.call(this, response.getReturnValue());
            else if(state == 'ERROR')
                reject.call(this, response.getError());
        });

        $A.enqueueAction(action);
    });
}

This way you could do something like hide a spinner when all of the promises have resolved. Hope this helps!

  • Does this approach affect the bundling of the enqueued actions or are they still all sent at the same time. Concerned that by putting the enqueue inside a promise would cause them to be executed independently – Eric Apr 21 '17 at 18:11
  • 1
    Thanks. I'll give it a shot this weekend and see how it goes. – Eric Apr 21 '17 at 18:14
3

You could also try Lax service component - https://github.com/ruslan-kurchenko/sfdc-lax

It is based on Promise API and provides an easy API to call server-side actions efficiently. In your case, you could enqueue list of actions like:

component.lax.enqueueAll([
   // { name : '...', params: {...}, options: {...} }
   { name: 'c.getContacts' }, 
   { name: 'c.getAccounts' },
   { name: 'c.getOpportunities' }
])
.then(results => {
   // results: [ [contacts], [accounts], [opportunities] ]
   const contacts = results[0];
   const accounts = results[1];
   const opportunities = results[2];
});

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.