Piotr Gajek
written byPiotr Gajek
posted on February 13, 2023
Technical Architect and Full-stack Salesforce Developer. He started his adventure with Salesforce in 2017. Clean code lover and thoughtful solutions enthusiast.

Then vs Async Await in LWC

Hi there! πŸ‘‹

This post expands on the topic of Promises in LWC.

JavaScript offers then and await as tools for managing asynchronous code. The then keyword is used with Promises and allows you to specify what should happen after a Promise is fulfilled. On the other hand, the await keyword can only be used within an asynchronous function and suspends the execution of the function until the specified Promise is resolved.

In this post, we'll dive deeper into the usage and benefits of then and await in JavaScript.

Let's begin!

Then vs Await

then/catch and async/await are very similar. Both are used to resolve the Promise.

javascript promise

What are the differences?

  • With await JavaScript will pause the function execution until the promise settles (resolve, reject). In short: asynchronous code behaves like synchronous.
function myPromiseFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Success');
        }, 2000);
    });
}

(async () => {
    console.log('Start');
    const result = await myPromiseFunction();
    console.log(`result - ${result}`);
    console.log('End');
})();

// Output
// Start
// result - Success
// End
  • With then the rest of the function will continue to execute, but JavaScript won't execute the then() callback until the promise settles (resolve, reject).
function myPromiseFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Success');
        }, 2000);
    });
}

(() => {
    console.log('Start');
    myPromiseFunction()
        .then(result => {
            console.log(`result - ${result}`);
        })
        .catch(error => {
            console.error(error);
        })
    console.log('End');
})();

// Output
// Start
// End
// result - Success

Promises Everywhere

Where we can find promises?

Apex Method returns a Promise

The apex method returns a Promise.

import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';

// Then/Catch
apexMethodName
    .then(result => {
        console.log(result);
    })
    .catch(error => {
        console.error(error);
    });

// Async/Await
try {
    const result = await apexMethodName();
} catch(error) {
    console.error(error);
}

Async

The async method returns a Promise.

async function myAsyncFunction() {
    return 'hello async promise!';
}

// Then
myAsyncFunction()
    .then(result => console.log(result));

// Await
const result = await myAsyncFunction();

Then

then return a Promise.

It immediately returns an equivalent Promise object, allowing you to chain calls to other promise methods. ~ MDN Web Docs

Each call to then() creates another step in the Promise chain, and if there’s an error at any point in the chain, the next catch() block will be triggered.

function myPromiseFunction() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Success');
        }, 2000);
    });
}

function myThenFunction() {
    return myPromiseFunction()
        .then(result => {
            return result;
        });
}

// Then
myThenFunction()
    .then(result => console.log(result));

// Await
const result = await myThenFunction();

Best practices

Do Not Combine Then And Await

As you know for the previous paragraphs then\catch and async\await are used to resolve Promises. You also know that then and async return a Promise. So?

We can combine then\catch and async\await and we can do something like that:

await myPromise().then(result => {
    console.log(result);
});

Let's add some context here:

❌❌❌

function myPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Success');
        }, 2000);
    });
}

async function myFun() {
    return await myPromise().then(result => {
        console.log(result);
    });
}

myFun();

It works! πŸ₯³ But does it mean that, it is correct?

No.

You should NOT combine then/catch and async/await!

  • Code is confusing. Devs who are not familiar with Promises very well, will not follow the code flow.
  • It's hard to understand. What we return here? A result, because of await or a Promise, because then is a last statement?
  • Keep It Simple, Stupid (KISS) rule is broken here. You do NOT need constructions like that in your code. Do not mix then and await for the same Promise.

How to do it right?

βœ…βœ…βœ…

function myPromise() {
    return new Promise((resolve, reject) => {
        setTimeout(() => {
            resolve('Success');
        }, 2000);
    });
}

// async function always return a promise
async function myFunAwait() {
    const result = await myPromise();
    console.log(`Await ${result}`);
}

// then function always return a promise
function myFunThen() {
    return myPromise().then(result => {
        console.log(`Then ${result}`);
    });
}

console.log('Start');
await myFunAwait(); // you can also use then/catch to resolve
await myFunThen(); // you can also use then/catch to resolve
console.log('End');

// Output
// Start
// Await Success
// Then Success
// End

Creating New Promises

You shouldn't need to create a promise explicitly to manually call resolve or reject. You shouldn't wrap a Promise within a Promise.

❌❌❌

import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';

callApex() {
    return new Promise((resolve, reject) => {
        apexMethodName
            .then(result => {
                resolve(result);
            })
            .catch(error => {
                reject(error);
            });
    })
}

βœ…βœ…βœ…

The refactor here is actually pretty simple:

import apexMethodName from '@salesforce/apex/Namespace.Classname.apexMethodReference';

async callApex() {
    const result = await apexMethodName();
    // when error occurs reject will be call
    return result; // when success resolve will be call
}

Nested Promises

Nested promises in JavaScript can lead to "callback hell" or code that is difficult to read and maintain. Nested promises can create a chain of asynchronous code that is hard to follow and debug. Additionally, if an error occurs in a nested promise, it can be difficult to properly handle and propagate the error to a higher-level error handler. This can result in silent failures or unexpected behavior in the application.

❌❌❌

async function getOrderDetailsWrapper(userId) {
    getUserData(userId)
    .then(user => {
        return getUserOrders(user.orderIds)
        .then(orders => {
            return getOrderDetails(orders[0].orderId)
            .then(details => {
                console.log(details);
            })
        })
    })
    .catch(error => {
        console.error(error);
    });
};

getOrderDetailsWrapper(userId);

βœ…βœ…βœ…

async function getOrderDetailsWrapper(userId) {
  try {
    const user = await getUserData(userId);
    const orders = await getUserOrders(user.orderIds);
    const details = await getOrderDetails(orders[0].orderId);
    console.log(details);
  } catch (error) {
    console.error(error);
  }
};

getOrderDetailsWrapper(userId);

If you have any questions, feel free to ask in the comment section below. πŸ™‚

Was it helpful? Check out our other great posts here.


Resources

Buy Me A Coffee