1

In my LWC, I am trying to call an Apex method and wait for it to finish. This is what it comes down to:

import apexMethodA from '@salesforce/apex/SomeControllerClass.apexMethodA';

export default class TradeWindow extends NavigationMixin(LightningElement) {

    initialJSfunction() {
        console.log('about to call waitForApexMethodA');
        this.waitForApexMethodA();
        console.log('finished calling waitForApexMethodA');
        
        // More processing depending on the output of apexMethodA.
    }

    async waitForApexMethodA() {
        console.log('about to call apexMethodA');
        try {
            await apexMethodA();
        }
        catch(error) {
            console.log(error);
        }
        console.log('finished calling apexMethodA');
        
        // Some more processing.
    }
}

To my surprise/horror, this is what the console log shows:

about to call waitForApexMethodA
about to call apexMethodA
finished calling waitForApexMethodA
finished calling apexMethodA

This is not what I expected and definitely not what I want. I have read lots of documentation about this and I had hoped that await would have the code wait until the Apex method returns with an answer, but apparently that is not how it works.

When I read Mozilla's documentation on this, it seems that await does not wait at all:

An await splits execution flow, allowing the caller of the async function to resume execution.

But in many other places, I read that await and async are the way to go.

My code is too complicated to solve this with a chain of .then's, I would like to solve it using await. How can I do that?

I tried to wrap the call to waitForApexMethodA in a Promise and await that, but of course, that doesn't work: as soon as await is encountered in waitForApexMethodA, control is returned to the caller. That is not what I want, initialJSfunction should only get control after all of waitForApexMethodA has completely finished.

6
  • You're missing an await before this.waitForApexMethodA(); Jan 6, 2021 at 17:23
  • @SachinHooda You cannot mark a code line with await, if the function/ method is not marked as async
    – arut
    Jan 6, 2021 at 17:34
  • @arut waitForApexMethodA is already marked async. Jan 6, 2021 at 17:37
  • @SachinHooda await operator can only be used inside an async function, failing this would throw a runtime error as "Can not use keyword 'await' outside an async function". The line that is marked with await should be inside an async function. So, initialJSfunction would also have to be marked as async.
    – arut
    Jan 6, 2021 at 17:44
  • 2
    This is standard for asynchronous events in JavaScript. async functions always return a Promise which is not guaranteed to be resolved before the function returns (although it typically will if and only if its actual execution contains no await calls). You need to propagate the usage of async/await or promises up through all functions which need to await completion of asynchronous operations, directly or indirectly. Without this, what you're suggesting you would require synchronous operations, which require full suspension of the main thread and would be strongly discouraged.
    – Mike Hill
    Jan 6, 2021 at 23:23

3 Answers 3

5

So it's a bit weird. The "waiting" happens within that aync function, not your initial js function.

consider this example

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('2 seconds');
    }, 2000);
  });
}
function resolveAfter4Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('4 seconds');
    }, 4000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result2 = await resolveAfter4Seconds();
  console.log(result2);
  const result1 = await resolveAfter2Seconds();
  console.log(result1);
}

asyncCall();
console.log('test');

The output of this would be

> "calling"
> "test"
> "4 seconds"
> "2 seconds"

Note that 4 seconds happens before 2, indicating that the await did in fact work here; however, 'test' was still printed out first. It is essentially the same as stringing promises together as you mentioned, just cleaner.

7
  • I guess I have to introduce my own Promise, which will call asyncCall, and then do an await on that before console.log('test') ? Jan 6, 2021 at 14:07
  • I'd say rather than having waitForApexMethodA just have asyncMethods where you do all of the apex calls dependent on each other, then in initialJSFunction you'd just call that one function. But know that function would not do the waiting.
    – stackasaur
    Jan 6, 2021 at 14:14
  • Here is a good example of this being done with multiple apex calls within LWC medium.com/@marcoalmodova/lwc-and-async-functions-1d6440080b01
    – stackasaur
    Jan 6, 2021 at 14:14
  • Wrapping the call in a Promise and doing another await does indeed not help, control is returned to the caller after the first await. How hard can it be to call a function and just wait for its completion? Jan 6, 2021 at 15:02
  • in my example, after resolveAfter4seconds() is called, the next code is not executed until it finishes. Meaning const result2 is assigned before we proceed
    – stackasaur
    Jan 6, 2021 at 15:04
1

Kudos to the other answerers and commenters. If I modify stackasaur's answer to this:

function resolveAfter2Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('2 seconds');
    }, 2000);
  });
}
function resolveAfter4Seconds() {
  return new Promise(resolve => {
    setTimeout(() => {
      resolve('4 seconds');
    }, 4000);
  });
}

async function asyncCall() {
  console.log('calling');
  const result2 = await resolveAfter4Seconds();
  console.log(result2);
  const result1 = await resolveAfter2Seconds();
  console.log(result1);
}

(async function(){
await asyncCall();
console.log('test');
})();

then the output will be:

calling
4 seconds
2 seconds
test

which is exactly what I want. Because of the complexity of my complete JS controller code (about 1200 LOC), I had trouble applying it to my situation.

0

You can mark the initialJSfunction as async and the method call inside it with await. This would make the JS code wait until apex method execution is completed. So, your code snippet would be as shown below:

async initialJSfunction() {
    console.log('about to call waitForApexMethodA');
    await this.waitForApexMethodA();
    console.log('finished calling waitForApexMethodA');
    
    // More processing depending on the output of apexMethodA.
}

Note that marking a JS method as async doesn't really make it asynchronous, unless there are statements within the method marked with await. So, if you have any synchronous code execution in the initialJSfunction to be executed before apex method call, you would need to have it before the statement marked with await. Also, as mentioned here, too much usage of async-await could lead to performance issues, so use it with some due-diligence.

You could also do a mix of async-await & .then construct as shown in the snippet below:

initialJSfunction() {
    console.log('about to call waitForApexMethodA');
    this.waitForApexMethodA().then((data) => {
         console.log('finished calling waitForApexMethodA');
   
         // More processing depending on the output of apexMethodA.
    });

    // Some synchronous code here if you want some processing before the apex method execution completes
}
5
  • If you read the last paragraph in my question, you'll see that I have already tried that. And it doesn't help. As soon as the first await is encountered, control is returned to the caller. The called function is still executed, but the calling function has already gone ahead. Jan 7, 2021 at 7:06
  • @SanderdeJong The last paragraph in your question didn't really give me a clear picture about the your actual code. Although I get what you have tried, I wasn't sure about your code. Anyways, I did not actually try the second code snippet given in my answer. But the first code snippet will work without any issues. Are you facing the problem with the first code snippet too?
    – arut
    Jan 7, 2021 at 7:26
  • No, the first will not work either. waitForApexMethodA will return control to the caller as soon as it encounters its first await. So the console log will show 'finished calling waitForApexMethodA' before 'finished calling apexMethodA'. The only thing that the await does is ensure that the remainder of waitForApexMethodA waits until apexMethodA has finished. But it will not halt execution of the caller. Jan 7, 2021 at 7:37
  • Scratch that last remark, upon altering stackasaur's answer I managed to get the order of execution that I want. I guess I was confused by the results of my own JS controller, which is apparently too complex. Jan 7, 2021 at 7:52
  • @SanderdeJong Okay, so my first code snippet will work correctly. I believe my second code snippet should work fine too if you carefully fix your existing code. I did just give it a try after your comment and it works fine for me.
    – arut
    Jan 7, 2021 at 8:02

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .