Lightning multiple enqueued actions execute action when all are done

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);

  • http://www.datchley.name/es6-promises/
  • http://www.datchley.name/promise-patterns-anti-patterns/
  • https://developers.google.com/web/fundamentals/getting-started/primers/promises#whats-all-the-fuss-about

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.)


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!