First--very well constructed question, and good investigation.
I was not aware of this issue with boxcarring in LWC.
I'm going to focus on a workaround rather than an actual setting, since I'm sure you've searched for that already.
What happens if you put your apex invocations inside setTimeout calls?
I know it's needlessly adding time, but you could add small delays like 50 msec or possibly even 0 mSec just to throw it on the stack.
The idea here is that Salesforce Lightning would have no place to gather all the simultaneous calls in one hidden object only to submit them all at once. When the active thread is building the page with your components, it's all happening in one thread.
Each imperative call is captured for a subsequent boxcar call.
However, if you start stacking calls, I don't see how boxcarring could intervene.
The initial thread would run to execution, and then presumably the boxcar thread would be called, and finally your setTimeouts.
I'm very anxious to hear if this approach works.
Update: Mixed results
I tried this out and given any number of apex method callouts, this approach un-boxed the first one or two callouts, but then all the rest got boxed up again.
This obviously made the biggest difference if the first callout was the longest, but without my code, all of the callouts ALWAYS were serially boxed.
Now, as it turns out delaying the call with the embedded setTimeout didn't cause this effect. It seems that simply calling a separate then-able ("sleeper()") in the Promise handler method was enough to disrupt the boxcarring of at least the first couple of apex callouts, regardless of whether there was an active setTimeout call.
Conclusion: This approach can definitely disrupt the boxcarring of the first two apex callouts, but is probably not useful since all the others remain boxed up. A more reliable solution may be to execute the callouts from Lightning/Javascript rather than via the Apex methods.
Here's the console log when each of the 4 callouts was set to a 1 second delay:
Call 1 Elapsed =1360
Call 2 Elapsed =1379
Call 3 Elapsed =2515
Call 4 Elapsed =2515
Total Elapsed =2515
Here's the console when with the longest calls starting first:
Call 2 Elapsed =3361 (3 second call)
Call 3 Elapsed =3527 (2 second call)
Call 4 Elapsed =3528 (1 second call)
Call 1 Elapsed =4354 (4 second call)
Total Elapsed =4354
In this best-case example, the shortest 2 calls were boxed up giving us the best possible improvement.
Here's the relevant code:
sleeper(ms) {
if (this.background === true) {
console.log('background=true');
return function (x) {
return new Promise(resolve => setTimeout(() => resolve(x), ms));
};
} else {
console.log('background=false');
return Promise.resolve('hello');
}
}
connectedCallback() {
console.log(this.startTime);
Promise.all( [
Promise.resolve('hello').then(()=> this.sleeper(1)).then(()=> requestWithSleep({sleepSeconds : 4})).then( ()=> console.log(`Call 1 Elapsed =${Date.now() - this.startTime}`)),
Promise.resolve('hello').then(()=> this.sleeper(1)).then(()=> requestWithSleep({sleepSeconds : 3})).then( ()=> console.log(`Call 2 Elapsed =${Date.now() - this.startTime}`)),
Promise.resolve('hello').then(()=> this.sleeper(1)).then(()=> requestWithSleep({sleepSeconds : 2})).then( ()=> console.log(`Call 3 Elapsed =${Date.now() - this.startTime}`)),
Promise.resolve('hello').then(()=> this.sleeper(1)).then(()=> requestWithSleep({sleepSeconds : 1})).then( ()=> console.log(`Call 4 Elapsed =${Date.now() - this.startTime}`)),
])
.catch(error => {
console.log('error loading page data:');
console.log(error);
})
.finally(() => {
console.log(`Total Elapsed =${Date.now() - this.startTime}`);
});
}
Option 1: use wire handlers. Instead of writing an Attribute name, you can write a function, and store the data in attributes, then call a common function when both results are available.
Example
attr1;
attr2;
@wire(getAttr1, {})
attr1result({data,error}) {
if(data) {
this.attr1 = data;
if(this.attr1 && this.attr2) {
this.doProcessing();
}
} // ... errors ... //
}
@wire(getAttr2, {})
attr2result({data,error}) {
if(data) {
this.attr2 = data;
if(this.attr1 && this.attr2) {
this.doProcessing();
}
} // ... errors ... //
}
doProcessing() {
// do something with attr1 and attr2 //
}
Option 2: Use a promise chain.
When calling them in order, keep in mind that this will cause a minor performance penalty.
connectedCallback() {
getAttr1({}).then(result => {
this.attr1 = result;
return getAttr2({})
}).then(result => {
this.attr2 = result;
}).finally(()=>this.doProcessing());
}
Best Answer
then block only ensures
this.apex1();
,this.apex2();
,this.apex3();
that the method is called in order. But there is no order when it gets executed.apex1() contains an async method. the async method will execute independently.hence you are getting the behavior.
Have a look at https://javascript.info/async-await
async/await functions in LWC