[SalesForce] Lightning Components: why are subsequent invocations of $A.enqueueAction() so super SLOW

I have an Lightning Component with an controller containing a sever side APEX-action like this

@AuraEnabled public static Object myServerSideAction_1() {
    return null;
}
@AuraEnabled public static Object myServerSideAction_2() {
    return null;
}

Now I'm invoking it from the client side controller:

var call1 = cmp.get("c.myServerSideAction_1");
call1.setCallback(this,function(res) {
    if(res.getState()=='SUCCESS') {
        var call2 = cmp.get("c.myServerSideAction_2")
        call2.setCallback(this,function(res) {
            if(res.getState()=='SUCCESS') {
                // ... optionally more nestings here
            }
        });
        $A.enqueueAction(call2);
    }
});
$A.enqueueAction(call1);

As you see: no heavy lifting done by APEX. The outer call returns very quickly as expected (milliseconds). The inner call takes some seconds and if I nest this even deeper, it becomes very very very very SLOW.

Why? Is there a kind of throttle in the framework which is giving me penalties for each nested call?

Appendix about setCallback()

This is a synthetic simplyfied extract of code running in a library residing in a static resource. I made this up just to look more like the examples from the documentation. In fact the code is neither running in a controller nor in a helper and instead of this I pass null to setCallback(). At the time I asked, I could not think that it could make any difference. Also I can't understand the sense and meaning of the first parameter called scope of setCallback(). It's suggested to pass this as scope, but in the context of my code I have no this.

I've separated the scope topic into this question Lightning Components: What is the meaning and purpose of the scope parameter of Action.prototype.setCallback()?

At this time I think 'scope' is not the reason for the performance degradation.

Research about possible serverside throttling done by Salesforce

Pointed out by @Sumuga there is a lot going on behind the courtains and the are also serverside throttling mechanisms in place as documented here: https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/controllers_server_background_actions.htm

Framework-Managed Request Throttling

The framework throttles foreground and background requests separately.
This means that the framework can control the number of foreground
requests and the number of background actions running at any time. The
framework automatically throttles requests and it’s not user
controlled. The framework manages the number of foreground and
background XHRs, which varies depending on available resources.

Even with separate throttling, background actions might affect
performance in some conditions, such as an excessive number of
requests to the server.

But it says "number of actions running at any time" and my interpretation is that it refers to simultaneous queued actions. As far as I understand, my code does the calls sequentially, i.e. my second $A.enqueueAction() happens after the first has been finished and the third after the second has finished and so on. I think somehow the server is remembering my enqueued history giving me extra (undocumented) penalties for calling multiple times in sequence. But I think that is unjustified be the reasons stated in the docs. If I do one call at a time, I should be not punished and I should be allowed to make as many call as I need or want. Or do I misunderstand this?

Best Answer

The code in the question is an abbreviated version of the real code and it doesn't have the issues described at all. I made this code up for the simplicity of this question while testing my real code and watching the issues.

Now what was the deal?

It turned out, that the reason behind all of this was a wrong usage of Promises without the required warping into $A.getCallback() as stated in the documentation:

https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_promises.htm

Adding $A.getCallback() solved this issue for me as I describe here so that others may avoid this trap: Simple example on how to use Promises to call @AuraEnabled Apex methods?

The most irritating fact which led me in the wrong direction was that the the first async result came back always fast. Only subsequent results came massively delayed...